Note:

This post is abondoned. I have stopped working on wordpress projects and as such I no longer keep track of wordpress related things.


Since the release of version 3.3, WordPress is using a brand new media uploading engine – Plupload. Plupload is an awesome uploader created by the developers of TinyMce. Depending on the device, it can use HTML5, Gears, Silverlight, Flash, BrowserPlus, or normal forms to provide features such as upload progress, auto image resizing, and asynchronous uploads.

Media Uploader

I am sure you have used “Upload Media” button at the top of wysiwyg editor in wordpress admin (see the image on the right hand side). WordPress core team used Plupload to make that media uploader. And, in this tutorial, we will find out how we, theme and plugin developers, can use it to handle image uploads in our admin screens. In this article, we will create a custom image uploader that uploads behind the scene (ajax), allows multiple images upload in one go, displays upload progress to the user, handles image resizing automatically, and works in metaboxes too. Yes, it’s one heck of an uploader.

Let’s start by writing down a list of features our uploader must have.

  • It allows both multiple and single image upload.
  • It uploads images asynchronously.
  • It displays upload progress.
  • It works in metaboxes.
  • We must be able to place more than one uploader in a page.
  • It handles image resizing automatically.
  • It displays thumbnails of images user has uploaded.
  • And optionally, it allows user to drag the thumbnails to change order of the images.

Long list? I know. But don’t worry, most of the needed functionality is provided by Pluploaded out of the box.

Before we start, here are the demos of final uploader.

Demo 1 shows pluploader in theme/plugin admin page.

Demo 2 shows pluploader in a metabox

Step 1: Include necessary javascripts and css styles.

Code

<?php  
function plu_admin_enqueue() {  
    if(!($condition_to_check_your_page))// adjust this if-condition according to your theme/plugin
       return;
    wp_enqueue_script('plupload-all');

    wp_register_script('myplupload', '/adjust-this-url/myplupload.js', array('jquery'));
    wp_enqueue_script('myplupload');

    wp_register_style('myplupload', '/adjust-this-url/myplupload.css');
    wp_enqueue_style('myplupload');
}
add_action( 'admin_enqueue_scripts', 'plu_admin_enqueue' );  
?>

Explanation

We use the admin_enqueue_scripts hook to include our scripts and styles. Please don’t include these files where you don’t need them. You can use an if-condition to test if this is the page you want to add this uploader to. We add Plupload scripts bundled with wordpress core. We also add the script and style we used to make this uploader: myplupload.js and myplupload.css

Step 2: Use the admin_head hook to print some script.

Code

<?php  
function plupload_admin_head() {  
// place js config array for plupload
    $plupload_init = array(
        'runtimes' => 'html5,silverlight,flash,html4',
        'browse_button' => 'plupload-browse-button', // will be adjusted per uploader
        'container' => 'plupload-upload-ui', // will be adjusted per uploader
        'drop_element' => 'drag-drop-area', // will be adjusted per uploader
        'file_data_name' => 'async-upload', // will be adjusted per uploader
        'multiple_queues' => true,
        'max_file_size' => wp_max_upload_size() . 'b',
        'url' => admin_url('admin-ajax.php'),
        'flash_swf_url' => includes_url('js/plupload/plupload.flash.swf'),
        'silverlight_xap_url' => includes_url('js/plupload/plupload.silverlight.xap'),
        'filters' => array(array('title' => __('Allowed Files'), 'extensions' => '*')),
        'multipart' => true,
        'urlstream_upload' => true,
        'multi_selection' => false, // will be added per uploader
         // additional post data to send to our ajax hook
        'multipart_params' => array(
            '_ajax_nonce' => "", // will be added per uploader
            'action' => 'plupload_action', // the ajax action name
            'imgid' => 0 // will be added per uploader
        )
    );
?>
<script type="text/javascript">  
    var base_plupload_config=<?php echo json_encode($plupload_init); ?>;
</script>  
<?php  
}
add_action("admin_head", "plupload_admin_head");  
?>

Explanation

The only reason we are not placing this script in an external file is that we need php and wordpress environment to output some data. In short, this is a base configuration array which will be used to configure uploaders.

Step 3: Placing html in your forms

Code

<?php  
// adjust values here
$id = "img1"; // this will be the name of form field. Image url(s) will be submitted in $_POST using this key. So if $id == “img1” then $_POST[“img1”] will have all the image urls

$svalue = ""; // this will be initial value of the above form field. Image urls.

$multiple = true; // allow multiple files upload

$width = null; // If you want to automatically resize all uploaded images then provide width here (in pixels)

$height = null; // If you want to automatically resize all uploaded images then provide height here (in pixels)
?>

<label>Upload Images</label>  
<input type="hidden" name="<?php echo $id; ?>" id="<?php echo $id; ?>" value="<?php echo $svalue; ?>" />  
<div class="plupload-upload-uic hide-if-no-js <?php if ($multiple): ?>plupload-upload-uic-multiple<?php endif; ?>" id="<?php echo $id; ?>plupload-upload-ui">  
    <input id="<?php echo $id; ?>plupload-browse-button" type="button" value="<?php esc_attr_e('Select Files'); ?>" class="button" />
    <span class="ajaxnonceplu" id="ajaxnonceplu<?php echo wp_create_nonce($id . 'pluploadan'); ?>"></span>
    <?php if ($width && $height): ?>
            <span class="plupload-resize"></span><span class="plupload-width" id="plupload-width<?php echo $width; ?>"></span>
            <span class="plupload-height" id="plupload-height<?php echo $height; ?>"></span>
    <?php endif; ?>
    <div class="filelist"></div>
</div>  
<div class="plupload-thumbs <?php if ($multiple): ?>plupload-thumbs-multiple<?php endif; ?>" id="<?php echo $id; ?>plupload-thumbs">  
</div>  
<div class="clear"></div>  

Explanation

We begin this code by adjusting some values for this specific uploader unit. We can request multiple file uploads and image resizing using those values.

We are using a hidden field to store all the image urls. This hidden field will be submitted in $_POST. Additionally, you must provide existing saved value (image urls) in $svalue variable. We added information like ajax nonce, multiple file upload, resize width and height in html tags (in classes, and in empty span tags – a bit unorthodox, but it will work perfectly. You can use data attributes here if you want to.)

Step 4: Setup ajax handler to handle asynchronous file upload.

Code

<?php  
function g_plupload_action() {

    // check ajax noonce
    $imgid = $_POST["imgid"];
    check_ajax_referer($imgid . 'pluploadan');

    // handle file upload
    $status = wp_handle_upload($_FILES[$imgid . 'async-upload'], array('test_form' => true, 'action' => 'plupload_action'));

    // send the uploaded file url in response
    echo $status['url'];
    exit;
}
add_action('wp_ajax_plupload_action', "g_plupload_action");  
?>

Explanation

We check the ajax nonce, handle file upload, and send the url of uploaded file as response.

Myplupload.js

Code

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 imgId = id1.replace("plupload-upload-ui", "");

            plu_show_thumbs(imgId);

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

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

            if ($this.hasClass("plupload-upload-uic-multiple")) {
                pconfig["multi_selection"] = true;
            }

            if ($this.find(".plupload-resize").exists()) {
                var w = parseInt($this.find(".plupload-width").attr("id").replace("plupload-width", ""));
                var h = parseInt($this.find(".plupload-height").attr("id").replace("plupload-height", ""));
                pconfig["resize"] = {
                    width: w,
                    height: h,
                    quality: 90
                };
            }

            var uploader = new plupload.Uploader(pconfig);

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

            });

            uploader.init();

            // a file was added in the queue
            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)));
            });

            // a file was uploaded
            uploader.bind('FileUploaded', function(up, file, response) {


                $('#' + file.id).fadeOut();
                response = response["response"]
                // add url to the hidden field
                if ($this.hasClass("plupload-upload-uic-multiple")) {
                    // multiple
                    var v1 = $.trim($("#" + imgId).val());
                    if (v1) {
                        v1 = v1 + "," + response;
                    } else {
                        v1 = response;
                    }
                    $("#" + imgId).val(v1);
                } else {
                    // single
                    $("#" + imgId).val(response + "");
                }
                // show thumbs 
                plu_show_thumbs(imgId);
            });
        });
    }
});

function plu_show_thumbs(imgId) {  
    var $ = jQuery;
    var thumbsC = $("#" + imgId + "plupload-thumbs");
    thumbsC.html("");
    // get urls
    var imagesS = $("#" + imgId).val();
    var images = imagesS.split(",");
    for (var i = 0; i < images.length; i++) {
        if (images[i]) {
            var thumb = $('<div class="thumb" id="thumb' + imgId + i + '"><img src="' + images[i] + '" alt="" /><div class="thumbi"><a id="thumbremovelink' + imgId + i + '" href="#">Remove</a></div> <div class="clear"></div></div>');
            thumbsC.append(thumb);
            thumb.find("a").click(function() {
                var ki = $(this).attr("id").replace("thumbremovelink" + imgId, "");
                ki = parseInt(ki);
                var kimages = [];
                imagesS = $("#" + imgId).val();
                images = imagesS.split(",");
                for (var j = 0; j < images.length; j++) {
                    if (j != ki) {
                        kimages[kimages.length] = images[j];
                    }
                }
                $("#" + imgId).val(kimages.join());
                plu_show_thumbs(imgId);
                return false;
            });
        }
    }
    if (images.length > 1) {
        thumbsC.sortable({
            update: function(event, ui) {
                var kimages = [];
                thumbsC.find("img").each(function() {
                    kimages[kimages.length] = $(this).attr("src");
                    $("#" + imgId).val(kimages.join());
                    plu_show_thumbs(imgId);
                });
            }
        });
        thumbsC.disableSelection();
    }
}

Explanation

For each uploader unit, we create a configuration array using the base configuration array, and pass it to the plupload.Uploader constructor. We then attach the following events:

  1. Init:
    This gets fired on the initiation of uploader object. This currently doesn’t do anything in our code.

  2. FilesAdded:
    This gets fired when user adds files using “Select Files” button. We use this event to show file unit containing filename and filesize. This unit has a small progress bar at the bottom. We also automatically start uploading selected files asynchronously.

  3. UploadProgress:
    This gets fired when we receive upload progress. We use it to display upload progress (both textual and graphic).

  4. FileUploaded:
    This gets fired when we receive upload complete notification from the server. We use it to fadeOut the file unit. In Step 4, we sent the url of uploaded file as response. We receive that url in this event. Depending on whether if we are using multiple files upload, we store this url in the hidden field. We then refresh the image thumbs below the select button area.

  5. Image thumbs at the bottom:
    We display thumbnails of the images which user has provided below the uploader. We also place a link to remove image below images. We use jQuery sortable ui to allow user to change order of the images by dragging thumbnails.

Myplupload.css

Code

.filelist {
    width: 60%;
}
.filelist .file {
    padding: 5px;
    background: #ececec;
    border: solid 1px #ccc;
    margin-bottom: 4px;
}
.filelist .fileprogress {
    width: 0%;
    background: #B7C53D;
    height: 5px;
}
.plupload-thumbs {

}
.plupload-thumbs .thumb {
    width: 50px;
    padding-right: 5px;
    float: left;
}
.plupload-thumbs .thumb img {
    width: 50px;
    height: 50px;
}
.ui-sortable  .thumb img {
    cursor: pointer;
}

Explanation

Css to style the uploader.

Handling Form Upload:

You will receive image urls in $_POST[“img1”] (see Step 3 to adjust “img1”) in csv format. For example: imgurl1,imgurl2,imgurl3

Conclusion:

In this article we have seen what an awesome addition Plupload is to the wordpress core. We can use it in our themes and plugins. We can even use it in metaboxes, which is awesome since normal file uploads don’t work in metaboxes. Now that you know how to use Plupload, you have the knowledge to create custom media uploaders for wordpress.