I was working with a client a few weeks ago. They wanted to create a customer portal that allowed their customers to upload files to their website. Once their clients have uploaded the files, they needed to be able to download and delete the files from the server. There were a few plugins in the WordPress repository that had some form of media uploading, but none of them seemed to use WordPress’ built-in functions. So, I dug in a bit and figured out how WordPress does it so I could easily replicate that functionality.
The main function that to know about is media_handle_upload(). It is the bread and butter of WordPress’ media upload. It takes up to 4 arguments and returns one variable.
From the media.php file:
@param string $file_id Index into the {@link $_FILES} array of the upload
@param int $post_id The post ID the media is associated with
@param array $post_data allows you to overwrite some of the attachment
@param array $overrides allows you to override the {@link wp_handle_upload()} behavior
@return int the ID of the attachment
For my basic needs, I only needed to worry about the first two arguments, $file_id and $post_id … and really the the $post_id is optional unless you want to associate the file as an attachment to a specific post (which I did).
The media_handle_upload() function is is part of wp-admin, so for good measure I included wp-admin/includes/admin.php. This probably isn’t necessary if you’re in the backend — but if you have a front-end page that your logged in clients will use, then you will need to do the same.
What The Client Needed
A membership portal that allowed clients to log in, go to the “upload” section, and upload a file. Then to be able to go to a “files” section to retrieve and delete the files. To do this I created two theme templates, “Client Files” and “Client File Upload”.
File Upload
First a simple form with a file input…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<form id="file-form" enctype="multipart/form-data" action="<?php echo $_SERVER['REQUEST_URI']; ?>" method="POST"> <p id="async-upload-wrap"> <label for="async-upload">Upload</label> <input type="file" id="async-upload" name="async-upload"> <input type="submit" value="Upload" name="html-upload"> </p> <p> <input type="hidden" name="post_id" id="post_id" value="1199" /> <?php wp_nonce_field('client-file-upload'); ?> <input type="hidden" name="redirect_to" value="<?php echo $_SERVER['REQUEST_URI']; ?>" /> </p> <p> <input type="submit" value="Save all changes" name="save" style="display: none;"> </p> </form>xhtml decode:true " ><form id="file-form" enctype="multipart/form-data" action="<?php echo $_SERVER['REQUEST_URI']; ?>" method="POST"> <p id="async-upload-wrap"> <label for="async-upload">Upload</label> <input type="file" id="async-upload" name="async-upload"> <input type="submit" value="Upload" name="html-upload"> </p> <p> <input type="hidden" name="post_id" id="post_id" value="1199" /> <?php wp_nonce_field('client-file-upload'); ?> <input type="hidden" name="redirect_to" value="<?php echo $_SERVER['REQUEST_URI']; ?>" /> </p> <p> <input type="submit" value="Save all changes" name="save" style="display: none;"> </p> </form> |
The form calls itself, so I needed a function to check if the form has been submitted and calls the media_handle_upload() function…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
if ( isset( $_POST['html-upload'] ) && !empty( $_FILES ) ) { require_once(ABSPATH . 'wp-admin/includes/admin.php'); $id = media_handle_upload('async-upload', 1199); //post id of Client Files page unset($_FILES); if ( is_wp_error($id) ) { $errors['upload_error'] = $id; $id = false; } if ($errors) { echo "<p>There was an error uploading your file.</p>"; } else { echo "<p>Your file has been uploaded.</p>"; } } |
I chose to hard code the post ID 1199 in this code. Not the best solution, but it serves its purpose. 1199 is the page ID of the page I created to have all the attachments associated with. This solves the next problem, how to display all the uploaded files. This way my client can download and delete them the files from an easy to access page.
Client Files
The client files page is another template, I actually set it as a private page and added some permissions so only the administrator can see the page. Since all the attachments uploaded should be associated with this page, I needed to create a special query that got all those attachments and display them in a neatly organized table…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
$args = array( 'post_type' => 'attachment', 'numberposts' => null, 'post_status' => null, 'post_parent' => 1199 //Post ID of Client Files page ); $attachments = get_posts($args); if ($attachments) { ?> <form id="file-form" action="<?php echo $_SERVER['REQUEST_URI']; ?>" method="POST"> <table style="width: 100%;"> <tr> <td style="width: 50px; text-align:center;">Delete?</td> <td style="width: 150px;">Attachment</td> <td style="width: 50px;">Username</td> <td style="width: 150px;">Name</td> </tr> <?php foreach ($attachments as $attachment) { ?> <tr> <td style="text-align:center;"><input type="checkbox" id="attachment_id" name="attachment_ids[]" value="<?php echo $attachment->ID ?>" /></td> <td><a href="<?php get_the_attachment_link($attachment->ID); ?>"><?php echo apply_filters('the_title', $attachment->post_title); ?></a></td> <td><?php the_author_meta( 'user_login', $attachment->post_author ); ?></td> <td><?php the_author_meta( 'first_name', $attachment->post_author ); ?> <?php the_author_meta( 'last_name', $attachment->post_author ); ?></td> </tr> <?php } ?> <tr> <td colspan="4"> <input type="submit" value="Delete" name="html-delete"> </td> </tr> </table> |
Just like the previous template, this form calls itself whenever the client tries to delete a file. So I needed to add a test to see if the delete function was called with the attachment ID(s) to delete. The form is using an array, so the client can delete multiple files at once.
1 2 3 4 5 6 7 |
if ( isset( $_POST['html-delete'] ) ) { if ( isset( $_POST['attachment_ids'] ) ) { foreach ($_POST['attachment_ids'] as $id) { wp_delete_attachment($id, true); } } } |
And that’s it… it is actually quite simple. Using the media_handle_upload() function reduces a lot of extra work. There is no sense in re-inventing the wheel and usually with WordPress you don’t have to.
I’ve stripped down this code a bit for this tutorial. You should be able to use it as a framework for creating your own pages. Obviously you should keep security in mind, making sure people have the right permissions to see these pages, etc.
Let me know if you have any questions, I’d love to hear your feedback!
Hi,
Thank you for sharing!
I was just trying to figure out what to do with these code snippets
Further help and guidance would be very much appreciated, Although I obviously understand that you might not have the time or patience to walk me through this.
Thanx again!
Hi James,
Are you stuck on anything in particular?
Lew
Thanks Lew. This is exactly what I needed for one of my projects. You are a life saver!!
-DK
Hey Darryl,
Glad I could be of assistance. I was afraid it was going to be a lot more complicated than it actually was. I love WordPress :).
Lew
I hear ya Lew. Thumbs up for WP!
One quick question for you. I’m trying to get the page to auto-refresh after the upload is deleted, but am having a heck of a time getting it to work. I wound up putting a Refresh button in for now, but it is not the cleanest of solutions. Do you have any ideas on how to incorporate this into your script?
Here’s what I tried that wasn’t working:
<?php
if ( isset( $_POST['html-delete'] ) ) {
if ( isset( $_POST['attachment_ids'] ) ) {
foreach ($_POST['attachment_ids'] as $id) {
wp_delete_attachment($id, true);
if ($errors) {
echo "There was an error deleting your file.”;
} else {
echo “Your file has been deleted.”;
header( ‘refresh: 4; url=http://**name of page in WP**.com/’ );
}
}
}
}
Thx!!
Darryl,
If you put the delete function above the query, you shouldn’t need to do a page refresh. In other words, the Client Files page should have the delete snippet above the list snippet.
Make sense?
Lew
Makes total sense — thanks again Lew! Delete the file before you paint the table. Working like a charm now.
Hi Lew,
Thank you for answering, i appreciate.
I just created a page as per your instructions, but i cannot figure out what to do next.
Thank You
Hi, love this!
But seems to be tiny mistake, !emptyempty( $_FILES ).
Hi Benedicte,
Thanks for pointing that out, it appears to be an issue with the Google Syntax Highlighter plugin though. The content does read, “!empty( $_FILES )”.
I’ll see what I can do to fix it and submit a patch.
Thanks again!
Would it be possible to define the folder where files are uploaded? I’ve searched the web for a long time about this but didn’t find any code that is compatible with this upload-script.
Best regards,
Troels
Troels,
You can use PHP to upload files to whatever folder you like, but this code is specifically designed to use WordPress’ upload functionality which uploads the files into the folder structure defined in WordPress.
There are a number of form plugins that will let you upload files. Check out the WordPress repository.
Lew
Thank you for the reply.
I am using WordPress, and I really like this simple code. It would just be great to be able to define the upload folder, besides in the WordPress admin-panel. So I can separate these files from other uploads done via the Media Uploader, but it might be difficult using this code.
Hi Troel,
I am pretty sure it’s not possible with this code. The point of the code is to use the same folder structure in the media uploader. Their page ID associates them with a specific page. That is how I am differentiating these uploads with other uploads. I just query the attachments on for the specific page to get a list of the files.
Lew
Hi Lew,
I have been looking for an example JUST LIKE THIS, but one that uses the built in facilities to RESIZE the images on the way thru – any thoughts on that? I’ve been trying to go to school on the way that the userphoto plugin does it, but it’s beyond me at the moment. I’ll have a few beers and try again :).
I was hoping it would be SO simple!
As background, I am developing a page for a client that is mostly a form page (automating a govt. process), that requires attachments (supporting photographs), which I need to upload. At some point down the track I will assemble all the collected information and generate a printed page or two, with the text and photos imbedded, hence the need to control the size …
Cheers
Dave
Dave,
The media_handle_upload should handle the normal resizing that WordPress does by default. Are you seeing something different?
I just tested on one of my development sites and I see the small/medium/large images normally associated with a Media Gallery upload.
Lew
Thanks Lew! This is a good framework.
This is exactly the information I’ve been looking for – Thanks for posting!
But, for some reason I can’t seem to make it work. I’m not sure if I’m putting the function in the right place.
I created a new page template and dropped the upload form into it. I tried putting the associated function in a plugin, in the theme’s function.php file, and then in the form-template page itself.
On the form page I can select the image for upload, but after I click the ‘upload’ button, the page just hangs, indefinitely. Nothing ever gets uploaded, and no errors are displayed.
It should be noted that I don’t need the “Client Files” page, so I ignored that template and function. Instead I just defined the ID of a standard post, and am hoping to call the images using the [gallery] shortcode.
Any help would be greatly appreciated. Thanks!
Oops! I figured it out.
I only changed the ID in the function, and not the form. I didn’t realize it was in 2 places. It’s working now and the [gallery] shortcode picks up the images just like it should.
Thanks again for posting!
On a related note, would it be difficult to edit this so that the images get attached to the same page / post that’s hosting the form, instead of specifying an ID?
While looking for information about customizing some of these functions, I came across a post by Mark Jaquith, a lead developer at WordPress, where he talks about the use of PHP $_SERVER variables not being safe to use in WordPress. I thought you would be interested.
Hey Paul,
If you use $post->ID it will use the same ID of the post/page that you’re on.
Thanks for that link from Jaquith…
I guess for this script it should be replaced with:
esc_url( get_permalink( $post->ID ) )
I’ll add it to my code above.
Lew
Actually looking at his comments, he says REQUEST_URI is save, but should still be esc_url’ed.
Hi Lew,
Thanks for the tip, but I can’t seem to make it work using $post->ID. I tried a straight replacement of 1199 in the original code, but the file doesn’t get attached. I’m assuming I need a slightly different format of either quotes or php tags or something, but I can’t seem to find the right combination. Any advice?
Oh, I wasn’t thinking…
In the form instead of:
Try:
Then the media upload function should look like:
and the attachment argument array should look like:
I *think* that should work (haven’t tested it).
Awesome! It works like a charm. Thank you so much for your help. This is exactly what I needed. :)
Hi,
This is a great post.
I am not much of a programmer. Actually I use a plugin wp-userfrontend to let users add posts from the frontend. But the plugin lacks image upload functionality. I just wanted to add the functionality so that users would be able to upload images as post attachments directly from the frontend. Is there an easier way? I mean which section of this code(that you provide) should I use to get it done?
Thanks already.
Hi. I have placed this in my files, looking good, however the link to the attachment is not working.
This: <a href="ID); ?>”>
is not displaying any link for the added attachment.
Displays:
Also curious if you have ever made function to add the attached file to an email to send to people? For my site the members area is hidden so I cannot attach the file to a post and send them as it will be password protected.
Thanks,
Wade
Hey Wade,
Not sure, you’d have to post your whole code for me to see what the problem is. If you want, post it to wordpress.pastebin.com and let me know the link. I can try to take a look later.
I have attached files as email attachments in PHP. Not sure if I’ve ever done using WordPress built-in functions though.
Lew
I’ve got this all working now, just looking to let the visitor submit the Media File Fields: Title, Caption and Description.
Has anyone set that functionality up?
Thanks,
Wade
Hi Wade,
The media_handle_upload takes a 3rd and 4th argument. The 3rd argument is an array that contains the information you want to add.
Hope that helps.
Lew
Great tutorial. One thing to note is that this approach is much better to run for file checking. You have so much more control.
Something else to note for the advanced guys who, like me, attempted to write this in as a front-end solution in a custom object oriented class file for a front end custom user profile page -> you will run in to issues if the code comes from outside the functions.php file.
The reason is due to the order in which things are loaded in to your pages. Although not written in my tutorial you can easily add this as a class file by simply creating an action or filter hook in your functions.php.
Why? This will keep your functions.php looking cleaner and be easier to read, and seperates your code for easy updates!
Great tutorial and solution Lew!
Hi Lew,
What do you think is the best method for allowing the client to upload multiple files at once?
Thanks for the great tutorial, its the best one out there!
Etha,
I haven’t tested it, but this code should work with multiple files at once… as far as the media_handle_upload() function is concerned. If not, you’d just need to stick it in a foreach.
If you want them to upload 5 possible files, then I’d just add 5 file input. But if you want them to select as many as they’d like, you’ll need to do something like what WordPress does with the Flash uploader. But I’d probably find an AJAX snippet that does the same stuff.
Lew
Lew,
I added the foreach but I think I have it in the wrong place, would you mind taking a quick peak?
http://pastebin.com/iR6fzwUg
The script as I have it is only saving the first upload.
Thanks Lew!
Hi Etha,
I finally got a chance to look at this more closely. It looks like the media_handle_upload() function expects a single file. In order to get around this (for multiple files), you’ll either need to create your own media_handle_upload() function that handles multiple files more gracefully, or you’ll need replicate the Flash uploader.
The main problem is that media_handle_upload() relies on $_FILES[$file_id][‘name’] to be a single value. But if you have two or more files, it becomes an array.
Lew
Actually, a foreach would work fine. You only need to feed it the file name and can run the function as many times as your server can handle (of which you should limit upload amount)
If you upload 10 files the array would like something like this:
$_FILES[‘image’][‘name’][0]
$_FILES[‘image’][‘name’][1]
$_FILES[‘image’][‘name’][2]…
So you could run a simple foreach($_FILES[‘image’][‘name’] as $value) to get the results you want.
Alternately using for($i=0;$i<sizeof($_FILES['image']['name']; $i++) would work easily as well.
For my own, similar tutorial I would be inclined to use the for() statement to allow me more control over the data.
I use if(!eregi(‘image/’, $_FILES[‘async-upload’][‘type’])) to check the file is of the type I want to allow, in this case an image file. Using a foreach to check the type and a for loop to add/insert using media_handle_upload() makes it a bit easier then writing a custom upload function.
Writing a function to resize and upload images outside of wordpress would be a pain, true. You can still use the built in media_handle_upload() for multiple files by using basic PHP loops to access the information. Let WP do the work for you.
Neil,
My concern is with the media_handle_upload() function itself, which actually uses $_FILE to determine the file name.
Line 199 of wp-admin/includes/media.php (latest trunk) reads:
$name = $_FILES[$file_id][‘name’];
So if you call media_handle_upload with an HTML array of files, it won’t get this properly.
Likewise the next line is:
$file = wp_handle_upload($_FILES[$file_id], $overrides, $time);
It appears to me that the wp_handle_upload() function also expects single files from the PHP $_FILES variable.
Do you have this working and am I misunderstanding you or were you just theorizing?
By the way, thanks for the thoughts.
Lew
I tink your are looking at it a little bit too linear. Ignore naming conventions such as $_FILE[]
Instead, look at it as simply this:
array(
‘name’ => ‘file_name’,
‘type’ => ‘file_type’,
‘size’ => ‘file_size,
…
);
So, all you are really going to do is feed the media_handle_upload() function the array with the containing data it seeks.
Also remember that $_FILES is actually a global when it is sent. Ultimately it is still just a simple array. So you can use several methods to manipulate it using PHP array functions like filter().
so if you have your form set up, in the most basic way, like
your $_FILE array will look like the above but slightly like this:
$_FILES = array(
array(
'name' => 'file_name',
'type' => 'file_type',
'size' => 'file_size,
...
),
array(
'name' => 'file_name',
'type' => 'file_type',
'size' => 'file_size,
...
),
...
)
To see what I mean create a form with a few fields and simply have the PHP do print_r() to display the array.
What I would do (will do for you) is reassign the $_FILES array to a new name – $files and then run a foreach that resets the globals:
$files = $_FILES["async-upload"];
for($i=0;$i<sizeof($files); $i++){
unset($_FILES["async-upload"]);
$_FILES["async-upload"] = $files[$i];
media_handle_upload('async-upload','');
}
When I have time I’ll toss together a working version that builds off of my own tutorial.
Here you go, with testing and love from unrelatedmedia.ca
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'] = $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];
}
for($i=0;$i<$number_of_files;$i++){
$_FILES["async-upload"] = $file_split[$i];
media_handle_upload('async-upload','');
unset($_FILES);
}
This was just to prove my point and doesn’t go through any file checking which you will still want to do. It works and uploads just fine.
Let me know your thoughts and/or how you would improve it!
Heya! I broke down my example much further in a tutorial. Feel free to use it to better understand my short sample above. I am pretty sure you get the idea, but just in case…
Upload Multiple Files to WordPress
Hi Lew,
thanks for sharing this great tutorial!
I have a similar problem to solve and maybe you can help out. What I’m trying to achieve is having an image/file uploader in the “add new post” page in the backend.
you can already post images through the media uploader but it’s not that easy, this is meant for a client and having a simple upload metabox would make things easier.
so far I found this http://www.billerickson.net/gallery-metabox/ plugin which is smart but it actually shows up the metabox only after you save the post. The developer says he can’t do otherwise becose he doesn’t know what the “add new post” page uses as ID so he can’t call the metabox on load.
would you know how to solve that problem or another way to have the upload images box in the “add new” page?
Hi Michel,
You’d probably have to make your own Javascript/AJAX to get this to work properly, it might be pretty complicated. You might want to check out my s2member Secure File Uploader plugin too. It basically recreates some of the functionality built into WordPress and re-uses some of the code. It might give you a better glimpse into the process.
Thanks Lew,
I’m not too worried about implementing AJAX and that would be a later step in order to give interactive feedback, what I’m most concerned is the way to find the general post ID used in core to make possible to add the upload box in the “add new” page.
I’ll give a look at your plugin and see if something clear things up to me :)
Hey Michel,
As I had mentioned, I do have a tutorial for multiple uploading of attachments at Upload Multiple Attachments
Now, i recently had reason to optimize the code and use it for a client project. At the bottom I attached a class file and sample test page template. What is important to note is the portability of a class.
On line 95 of the class you will see this:
media_handle_upload(‘async-upload’,”);
That empty value is where you place the post id to upload an attachment to.
You would use the form to send the post/page ID. The class would then place it using something like (which you would modify in the class):
media_handle_upload(‘async-upload’,$_POST[‘gallery’]);
I could have made an if(empty($_POST[‘gallery’])) but I was just a little lazy, to be honest. It would be a good thing to add and then yuo wouldn’t need to edit the class file ever.
******
With Autosave off your best approach to uploading media using meta is simply that – use the metabox. Save it as json data.
If you want the client to actually upload an image/attachment from within the WordPress administration, well, it can be done using my class file and custom meta box set up as the form. HOWEVER:
Put autosave on, and run a check if current post exists and/or the title exists (which is usually a trigger for autosave) before being able to upload. If the check shows true then you can safely upload the image/attachment to the current post draft or published page.
Thanks… :)
i am developing a plugin in which i required to change logo of selected theme.. you code did help me..
but i there is any easy way to develop your plugin page and to upload a logo from there to change it in the website then it would be so appreciable…
Hy…
I want make a media uploader like wordpress in my cms,
but there always show some error message about “File Upload Error”
can you help me to make it to be secure and easily?
hi,
I want to use the default wordpress uploader on front end that we used in backend to upload images and set featured image like in post add edit screen.
Can you help
Thanks
Hi Sajid, The new uploader is a bit more complicated. I haven’t looked at it too much.
Very good tutorial. Applied perfectly in a project here and I wonder if I can translate this tutorial on our blog and add credits.
Thank you.
We await return.
Go for it.