Le blog

Uploader des gros fichiers dans WordPress avec Plupload

Dans le cadre d’une refonte de site internet avec WordPress, un de nos clients avait besoin d’un développement sur mesure : les rédacteurs du site doivent pouvoir régulièrement uploader des fichiers assez lourds (zip de plus de 50Mo) dans des custom posts.

Dans sa version précédente (non Wordpress), le site internet utilisait une applet Java mais celle ci avait plusieurs inconvénients (présence de Java sur le poste de l’auteur, fonctionnalité régulièrement bloquée par des mises à jour…). Il fallait également tenir compte des limitations de WordPress et de l’hébergement en termes de tailles de fichiers (upload_max_filesize et post_max_size). Qui ne sont pas toujours configurables, en particulier sur des mutualisés.

La solution semblait un peu compliquée à trouver mais elle existe 🙂 Depuis sa version 3.3, WordPress embarque la librairie d’upload Plupload et partant de cela, j’ai trouvé un petit tuto en anglais pour l’utiliser (ici pour ceux que ça intéresse). Mais cela ne répondait pas totalement  à notre problématique car les fichiers de notre client étaient beaucoup trop. Heureusement, Plupload inclue une option pour envoyer les fichiers par paquets (chunk). Bon il a fallu un peu ruser mais au final ça fonctionne bien, et sans module supplémentaire. Let’s go !

Appel des scripts JS et des feuilles de la CSS

<?php
function plupload_admin_enqueue() {
    if( ! ( $condition_to_check_your_page ) ) // ajustez cette condition selon votre plugin/thème
        return;
    wp_enqueue_script( 'plupload-all' );
    wp_register_script( 'myplupload', '/votre-chemin/myplupload.js', array( 'jquery' ) );
    wp_register_style( 'myplupload', '/votre-chemin/myplupload.css' );
}
add_action( 'admin_enqueue_scripts', 'plupload_admin_enqueue' );

Tout d'abord, nous allons utiliser le hook admin_enqueue_scripts pour inclure nos scripts et styles. Nous ajoutons également une condition pour éviter le chargement de ces éléments en dehors du contexte où ils seront utilisés.
Les scripts chargés sont le javascript Pupload du coeur de WordPress et nos scripts spécifiques : myupload.js et myupload.css.

Insertion de données JS via le hook admin_head

<?php function plupload_admin_head() { // tableau de config pour plupload $plupload_init = array( 'runtimes' => 'html5,silverlight,html4',
        'browse_button' => 'plupload-browse-button',
        'container' => 'plupload-upload-ui',
        'drop_element' => 'dropzone',
        'file_data_name' => 'async-upload',
        'chunk_size' => '9mb', // taille maximum de chaque paquet
        'url' => admin_url( 'admin-ajax.php' ),
        'silverlight_xap_url' => includes_url( 'js/plupload/plupload.silverlight.xap' ),
        'filters' => array( array( 'title' => __( 'Allowed Files' ), 'extensions' => 'zip' ) ),
        'urlstream_upload' => true,
        'multi_selection' => false,
        // données post supplémentaires à envoyé à notre hook ajax
        'multipart_params' => array(
            '_ajax_nonce' => "", // sera ajouté par l'uploader
            'action' => 'plupload_action',
            'file' => 0 // sera ajouté par l'uploader
        )
    );
?>
<script type="text/javascript">  
    var my_ajaxurl = "<?php echo admin_url( 'admin-ajax.php' ); ?>";
    var base_plupload_config = <?php echo json_encode( $plupload_init ); ?>;
</script>  
<?php
}
add_action( "admin_head", "plupload_admin_head" );

Ces éléments de configuration seront utilisés par l'uploader. Pour plus d'infos sur les paramètres disponibles vous pouvez aller sur la doc de Plupload (http://www.plupload.com/docs/).
Dans notre cas le paramètre important est chunk_size qui va définir la taille maximum de chaque paquet. Si le fichier envoyé est plus grand il sera éclaté.

Ajout du HTML

<div class="wrap">















<h2>Import</h2>































<div class="dropzone" id="zip_filedropzone">















<div class="uploader">















<div class="upload-ui">















<h3 class="upload-instructions drop-instructions"><?php _e( 'Drag and drop your zip file here' ); ?></h3>















                
<?php _e( 'or' ); ?>















<div class="plupload-upload-uic hide-if-no-js" id="zip_fileplupload-upload-ui">  
                    <input id="zip_fileplupload-browse-button" type="button" value="<?php esc_attr_e('Select File'); ?>" class="button" />
                    <span class="ajaxnonceplu" id="ajaxnonceplu<?php echo wp_create_nonce( 'zip_filepluploadan'); ?>"></span>















<div class="filelist"></div>















</div>















</div>















</div>















</div>































<div id="import_progress" class="filelist">















<div class="file" id="current_file">
            <span><?php _e( 'Zip extraction...' ); ?></span>















<div class="fileprogress"></div>















</div>















</div>















</div>

Pour la refonte WordPress du site de notre client, nous avons inséré ce code sur une page spécifique pour l'import dans le back-office. Dans un souci d'homogénéité avec le fonctionnement de WordPress pour l'upload de médias, nous avons mis une balise div pour une zone de drag and drop, qui contient également un bouton pour directement parcourir sur le poste de l'utilisateur. Les identifiants font directement au paramètres Plupload définis précédemment.

Handler pour l'envoi du fichier en Ajax

<?php function my_upload_dir( $upload ) { $upload['subdir'] = '/mon-rep'; $upload['path'] = $upload['basedir'] . $upload['subdir']; $upload['url'] = $upload['baseurl'] . $upload['subdir']; return $upload; } function my_allowed_mime_types ( $existing_mimes=array() ) { $existing_mimes['part'] = 'application/octet'; return $existing_mimes; } function plupload_action() { $fileid = $_POST["fileid"]; if (empty($_FILES) || $_FILES[$fileid . 'async-upload']['error']) : die('{"OK": 0, "info": "Failed to move uploaded file."}'); endif; // 5 minutes par paquet devrait être suffisant @set_time_limit(5 * 60); // Changement du répertoire de destination add_filter('upload_dir', 'my_upload_dir' ); // Définition du fichier partiel $upload_dir = wp_upload_dir(); $filename = get_current_user_id() . '-' . $_FILES[$fileid . 'async-upload']['name']; $_FILES[$fileid . 'async-upload']['name'] = $filepart = $filename . '.part'; $filetmp = $filename . '.tmp'; // vérification ajax nonce check_ajax_referer( $fileid . 'pluploadan' ); // Test si c'est un paquet $chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0; $chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 0; // Nous avons défini un fonction pour autoriser les types de fichier ".part" add_filter('upload_mimes', 'my_allowed_mime_types' ); // Upload du fichier partiel $status = wp_handle_upload( $_FILES[$fileid . 'async-upload'], array( 'test_form' => false, 'action' => 'plupload_action' ) );
        
    // Construction du fichier définitif au fur et à mesure de la réception des paquets
    $out = @fopen( $upload_dir['path'].'/'.$filetmp, $chunk == 0 ? "wb" : "ab");
    if ($out) :
        $in = @fopen( $upload_dir['path'].'/'.$filepart, "rb");

        if ($in) :
            while ($buff = fread($in, 4096))
                fwrite($out, $buff);
        else :
          die('{"OK": 0, "info": "Failed to open input stream."}');
        endif;
        @fclose($in);
        @fclose($out);
        @unlink($upload_dir['path'].'/'.$filepart);
    else :
        die('{"OK": 0, "info": "Failed to open output stream."}');
    endif;

    // Renommage du fichier avec son nom d'origine
    if (!$chunks || $chunk == $chunks - 1) :
        rename( $upload_dir['path'] . '/' . $filetmp, $upload_dir['path'] . '/' . $_REQUEST["name"]);
    endif;
        
    // Renvoi du chemin du fichier uploadé
    echo $upload_dir['path'] . '/' . $_REQUEST["name"];
    exit;
}
add_action( 'wp_ajax_plupload_action', 'plupload_action' );  

Bon voilà le gros morceau. Outre les vérifications classiques (fichier existant, nonce...), nous avons changé le répertoire de destination pour ne pas polluer le répertoire uploads de WordPress.
Nous valorisons ensuite une variable afin de tester plus tard si l'upload courant est le fichier intégral ou une partie.
Point important : pour chaque paquet, Plupload génère un fichier blob sans extension. WordPress n'accepte pas ce type d'upload. Pour contourner cette difficulté, nous avons renommé le fichier en blob.part et autorisé ce type mime avec le filtre upload_mimes.
Nous utilisons ensuite le système de fichiers PHP pour additionner les différentes parties du fichier au fur et à mesure de l'upload.
Enfin, nous renommons le fichier avec le nom de fichier d'origine.

myupload.js

jQuery.fn.exists = function() {  
    return jQuery(this).length > 0;
}
jQuery(document).ready(function($) {

    if ($(".plupload-upload-uic").exists()) {
        var pconfig = false;
        $(".plupload-upload-uic").each(function() {
            var $this = $(this);
            var id1 = $this.attr("id");
            var fileId = id1.replace("plupload-upload-ui", "");

            pconfig = JSON.parse(JSON.stringify(base_plupload_config));

            pconfig["browse_button"] = fileId + pconfig["browse_button"];
            pconfig["container"] = fileId + pconfig["container"];
            pconfig["drop_element"] = fileId + pconfig["drop_element"];
            pconfig["file_data_name"] = fileId + pconfig["file_data_name"];
            pconfig["multipart_params"]["fileid"] = fileId;
            pconfig["multipart_params"]["_ajax_nonce"] = $this.find(".ajaxnonceplu").attr("id").replace("ajaxnonceplu", "");

            var uploader = new plupload.Uploader(pconfig);

            uploader.bind('Init', function(up) {
            });

            uploader.init();

            // Fichier ajouté à la file
            uploader.bind('FilesAdded', function(up, files) {
                $.each(files, function(i, file) {
                    $this.find('.filelist').append('














<div class="file" id="' + file.id + '"><b>' +
                    file.name + '</b> (<span>' + plupload.formatSize(0) + '</span>/' + plupload.formatSize(file.size) + ') ' + '














<div class="fileprogress"></div>














</div>














');
                });
                up.refresh();
                up.start();
            });

            uploader.bind('UploadProgress', function(up, file) {

                $('#' + file.id + " .fileprogress").width(file.percent + "%");
                $('#' + file.id + " span").html(plupload.formatSize(parseInt(file.size * file.percent / 100)));
            });
            
            uploader.bind("ChunkUploaded", function(up, file, response){
                // Retroune un code 200 si l'upload est correct
                if(response.status != null && response.response != null)
                {
                    if(response.status != "200" || (response.response.indexOf("Error") != -1))
                    {
                        if(response.response.indexOf("Error") != -1)
                        {
                            $("div.error").show().html('
'+response.response+'
');
                        }
                        else
                        {
                           // Notifie l'erreur
                           $("div.error").show().html('
There was an error uploading your file '+ file.name +' Support has been notified.
');
                        }
                        $('#' + file.id).addClass("cancelled");
                        uploader.stop();
                    }
                }    
            });

            // Fichier uploadé
            uploader.bind('FileUploaded', function(up, file, response) {
                $('#zip_filedropzone').fadeOut();
                $('#' + file.id).fadeOut();
                $('#import_progress').fadeIn();
                zip_path = response["response"];
                if ( response !== '' ) {
                    $.ajax({
                        type: 'POST',
                        url: my_ajaxurl,
                        data: { action: "my_unzip", file_path: zip_path },
                        success: function( response ) {
                            // Traitement spécifique à effectuer une fois le fichier importé
                        }
                    });
                }
            });
        });
    }    
    
    // Définition de la zone de drag & drop et des événements associés
    var droppable = $('#zip_filedropzone'),
        lastenter;

    droppable.on("dragenter", function (event) {
        lastenter = event.target;
        droppable.addClass("drag-over");            
    });

    droppable.on("dragleave", function (event) {
        if (lastenter === event.target) {
            droppable.removeClass("drag-over");
        }
    });
    
    droppable.on("drop", function (event) {
        droppable.removeClass("drag-over");
    });
   
});

Autre morceau important : le traitement JavaScript. Ici nous initialisons l'uploader et utilisons ses différents événements pour traiter la barre progression et traiter le fichier reçu. Là aussi vous pouvez vous reporter à la documentation de Plupload pour les événements disponibles.

myupload.css

.filelist {
    width: 60%;
    margin:80px auto 0;
}
.filelist .file {
    padding: 5px;
    background: #ececec;
    border: solid 1px #ccc;
    margin-bottom: 4px;
}
.filelist .fileprogress {
    width: 0%;
    background: #0073AA;
    height: 5px;
    margin-top:5px;
}
.dropzone{
    height:200px;
    text-align:center;
    border:4px dashed #B4B9BE;
}
.dropzone.drag-over{
    border-color:#83b4d8;
}
.uploader{
    margin:50px auto 0;
}
.plupload-upload-uic input.button{
    height:46px;
    line-height:44px;
    padding:0 36px;
    font-size:14px;
}
.uploader p{
    font-size:12px;
}
#import_progress{
    text-align:center;
    display:none;
}

On termine léger avec la CSS utilisée.

Voilà, libre à vous de personnaliser selon vos besoins, en utilisant par exemple la fonctionnalité de multi-upload présente nativement dans Plupload ou, ce qui a été notre cas, en effectuant un traitement à la fin de l'upload.

Enfin et bien sûr, nous restons à votre disposition pour toute autre info sur la création de sites avec WordPress.

Publié dans Création d'un blog ou site Wordpress
2ebaf0a2e6dc3b9cc7b509717224f182<<<<<<