Jun
14
2011

WordPress Upload Multiple Attachments

Quick Tutorial on Uploading Multiple Files in WordPress

Uploading multiple files in WordPress is something that was mentioned in the post at lewayotte.com. I felt a need to answer the challenge of uploading multiple attachments in WordPress while still using media_handle_upload() instead of a custom PHP script. I’m never one to reinvent the wheel but I am someone whole likes to find ways to make that wheel better!

File Checking

This tutorial doesn’t go over file checking or much validation other then making sure the files are of the type we wish to allow. It is simply a demonstration of the possibilities on using media_handle_upload() to upload multiple files.

The PHP $_FILES Array

What the author of lewayotte.com overlooked was that files sent from a form arrive as an array of variables. This is not really a fault of the author, as we all sometimes forget that even the most complicated function can be broken apart into the most basic form of programming. This is the case with the PHP $_FILES. The array, in it’s basic form, looks like this:

array(
        'type' => '',
	'tmp_name' => '',
	'error' => '',
	'name' => '',
	'size' => ''
     );

That’s it. The mysterious PHP $_FILES is really just a simple associative array. Pretty cool, right? Yet this explains only one file array being sent. What does it look like with multiple files? Like this:

array(
        'type' => array([0]=file_1, [1]=file_2, [2]=file_3...),
	'tmp_name' => array([0]=file_1, [1]=file_2, [2]=file_3...),
	'error' => array([0]=file_1, [1]=file_2, [2]=file_3...),
	'name' => array([0]=file_1, [1]=file_2, [2]=file_3...),
	'size' => array([0]=file_1, [1]=file_2, [2]=file_3...)
     );

The only thing that truly defines $_FILES as anything special is that it is a super global. Being a super global means that it can be accessed from any script on the server at any time. This is actually rather important because media_handle_upload() actually uses the super global $_FILES array variables. Using PHP unset() we actually take advantage of this, as you will soon see.

Pretty straight forward in thought, but sometimes hard to wrap our heads around. You see, a PHP foreach() wouldn’t work effectively to traverse this array. We simply wouldn’t have the amount of control we would have if we used a PHP for() function.

Why does the method of walking the array matter?

Remember that we are using WordPress to upload multiple files? Well, the media_handle_upload() function expects certain variables to be sent. The first, and most important is the file array to be uploaded. Yet it only accepts a single file! To upload multiple files we need to seperate all the individual files from the example array above so that we can insert them one by one. The media_handle_upload variables look kinda like this in the function:

media_handle_upload('SINGLE_FILE_ARRAY',POST_ID);

Upload Multiple Files Function

Now that the basics are established we are ready to put it to work.

require_once(ABSPATH . 'wp-admin/includes/admin.php');
$file = $_FILES['async-upload'];
unset($_FILES);
$file_split;
$number_of_files = sizeof($file['name']);

The first step we do, especially if not working from the administration side of WordPress, is load the file with the actual media_handle_upload() function. Then, because I prefer easier code to read, I place the array of files sent by the form into a new, simplified variable and call it $file. Once the array is reassigned I can get rid of it! In truth, I will actually be recreating the $_FILES super global and giving the values a single file at a time instead of an array in an array in an array in…

Right then.

$file_split was created to hold the array of all the files in a much easier to transverse manner on an individual basis. It will basically look something like this:

array(
  [0] = array(
        'type' => '',
	'tmp_name' => '',
	'error' => '',
	'name' => '',
	'size' => ''
        )
  [1] = array(
        'type' => '',
	'tmp_name' => '',
	'error' => '',
	'name' => '',
	'size' => ''
        )
...
     );

See that? Each array key is now an entire list of all the variables we need for each file. We do that by using the following loop:

//split the $_FILE into an easy to iterate
//array
for($i=0;$i<$number_of_files;$i++){
    $file_split[$i]['name'] = "uID_" . $user_id . "_" . $file['name'][$i];
	$file_split[$i]['type'] = $file['type'][$i];
	$file_split[$i]['tmp_name'] = $file['tmp_name'][$i];
	$file_split[$i]['error'] = $file['error'][$i];
	$file_split[$i]['name'] = $file['name'][$i];
	$file_split[$i]['size'] = $file['size'][$i];

}

I added “uID_” . $user_id . “_” in order to seperate files down the road should the need ever arise.

Good stuff. Lets use the file array to actually do something cool…like uploading multiple files! Since we created a far easier to use array of all the files we can use another for() loop to insert in to media_handle_upload():

//run through and upload each file
for($i=0;$i<$number_of_files;$i++){
  $_FILES["async-upload"] = $file_split[$i];
    if(!Uploaded_Mime_Type()) {
          $message = "The uploaded file is not an accepted file type please upload a valid file!";
	  $message = str_replace(" ","%20",$message);
          break;
	  }
     else{
         //it is an image, upload it
         media_handle_upload('async-upload','');
         unset($_FILES);
        }
   }

What about error handling? Glad you asked. By using a very simple if() statement we can check the file type:

    if(!Uploaded_Mime_Type()) {
          $message = "The uploaded file is not an accepted file type please upload a valid file!";
	  $message = str_replace(" ","%20",$message);
          break;
	  }
	else{
         //it is an image, upload it
         media_handle_upload('async-upload','');
         unset($_FILES);
        }

You might be wondering what Uploaded_Mime_Type() is. Well, that’s my bonus to you! This function checks a file type against an array of accepted file types. You can comment out all items in the array that don’t fit the type of file you want to allow:

function Uploaded_Mime_Type() {
        //edit this array to limit accepted file types
        $mime_types = array(
            'txt' => 'text/plain',
            'htm' => 'text/html',
            'html' => 'text/html',
            'php' => 'text/html',
            'css' => 'text/css',
            'js' => 'application/javascript',
            'json' => 'application/json',
            'xml' => 'application/xml',
            'swf' => 'application/x-shockwave-flash',
            'flv' => 'video/x-flv',

            // images
            'png' => 'image/png',
            'jpe' => 'image/jpeg',
            'jpeg' => 'image/jpeg',
            'jpg' => 'image/jpeg',
            'gif' => 'image/gif',
            'bmp' => 'image/bmp',
            'ico' => 'image/vnd.microsoft.icon',
            'tiff' => 'image/tiff',
            'tif' => 'image/tiff',
            'svg' => 'image/svg+xml',
            'svgz' => 'image/svg+xml',

            // archives
            'zip' => 'application/zip',
            'rar' => 'application/x-rar-compressed',
            'exe' => 'application/x-msdownload',
            'msi' => 'application/x-msdownload',
            'cab' => 'application/vnd.ms-cab-compressed',

            // audio/video
            'mp3' => 'audio/mpeg',
            'qt' => 'video/quicktime',
            'mov' => 'video/quicktime',

            // adobe
            'pdf' => 'application/pdf',
            'psd' => 'image/vnd.adobe.photoshop',
            'ai' => 'application/postscript',
            'eps' => 'application/postscript',
            'ps' => 'application/postscript',

            // ms office
            'doc' => 'application/msword',
            'rtf' => 'application/rtf',
            'xls' => 'application/vnd.ms-excel',
            'ppt' => 'application/vnd.ms-powerpoint',

            // open office
            'odt' => 'application/vnd.oasis.opendocument.text',
            'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
        );
		if(in_array($_FILES['async-upload']['type'],$mime_types)){
		  return true;
		}
		else{return false;}
	}

Uploading Multiple files in WordPress: Complete Function

Alright. The entire function should look a little something like this:

function my_upload_function(){
//multiple file handling
require_once(ABSPATH . 'wp-admin/includes/admin.php');
$file = $_FILES['async-upload'];
unset($_FILES);
$file_split;
$number_of_files = sizeof($file['name']);

//split the $_FILE into an easy to iterate
//array
for($i=0;$i<$number_of_files;$i++){
    $file_split[$i]['name'] = "uID_" . $user_id . "_" . $file['name'][$i];
	$file_split[$i]['type'] = $file['type'][$i];
	$file_split[$i]['tmp_name'] = $file['tmp_name'][$i];
	$file_split[$i]['error'] = $file['error'][$i];
	$file_split[$i]['name'] = $file['name'][$i];
	$file_split[$i]['size'] = $file['size'][$i];

}
//run through and upload each file
for($i=0;$i<$number_of_files;$i++){
  $_FILES["async-upload"] = $file_split[$i];
    if(!Uploaded_Mime_Type()) {
          $message = "The uploaded file is not an accepted file type please upload a valid file!";
	  $message = str_replace(" ","%20",$message);
          break;
	  }
     else{
         //it is an image, upload it
         media_handle_upload('async-upload','');
         unset($_FILES);
        }
   }
}

You can download the source file which includes a fully commented class and a sample page template.

As you can see, uploading multiple files to WordPress is only as complicated as you want to make it. I hope you enjoyed this tutorial and welcome any feedback or improvements you may have!

Related posts:

About the Author: Neil Davidson

Neil Davidson writes articles and tutorials about web development. He is happily married with three dogs, two cats and an ominous spirit in his home.

9 Comments + Add Comment

  • Hi Neil,

    I followed you here from lewayotte.com. Thank you for breaking everything down. Would you mind providing a complete example, say in a 2010/2011 page template (where doesnt really matter). I really think that would help me, and I’m sure others, understand this better.

    Thanks!

    • I’m not entirely certain what you mean by 2010/2011 page template . If you mean including a form, that is actually the same form used in the Upload Attachment tutorial.

  • Hi Neil, this is a great tutorial, thanks for sharing, I have a question can implement this script to upload files from the profile of each user through the backend of wordpress

    • Absolutely you can. The only real difference is you don’t need to call on the extra classes because WordPress administration loads them by default. So require_once(ABSPATH . ‘wp-admin/includes/admin.php’) wouldn’t need to be called if you are working from the backend.

  • what is the license for your script, can i use it in commercial web product?

    • Absolutely, Vian. Any tutorial on my site is completely free and open source.

  • This is great & very useful! thanks so much neil!

    But, any chance you could help me sort out how to limit the number of files the user can upload? I’m trying to limit it to 3. i’ve played around with ‘max_file_uploads’ but to no avail.

    thanks !

    • Max_File_Size doesn’t affect the entire upload package (all files uploaded) but the maximum size of “each” file.

      Easiest PHP approach I would take is to add a simple if statement before the for loop:

      if($number_of_files > 3){
      $message = “Maximum allowed file uploads is 3.”;
      }else{ //do the processing }

      Before it gets to the PHP you can even stop it at the client side level with jquery, iterate and count before sending to server side:

      $(‘#fileElementId’).get(0).files;

      Of course you could create a simple file input with a limit of 1 file and use jquery to “add” another input box to upload a second file, then a third and have jquery dynamically add it to the form. This last approach would be the most user friendly.

  • Nicely done. Well explained. Thanks!

Leave a comment