Fork me on GitHub

article

Classes for file uploading in PHP

December 22, 2006 | PHP Scripting

Surprisingly, I still get a lot of referrals from The Stickman for his work on a multi-file javascript upload helper on which I briefly assisted on Safari compatibility. The script adds and removes file upload form elements as requested by the user. So a form can be designed to handle as many uploads as you’d like and the user can have up to that many files uploaded to the server. It really is a nice script.

I posted a comment there that linked back to a file upload script that I wrote. Well, since then I’ve written something much better and its about time to show it off. I actually wrote it a while back and recently cleaned it up to go all PHP5 with it.

The script works in two parts. In OOP fashion I separated out tasks that could be shared by other processes and that gave me an upload class and a mime-type class.

With these two classes, processing uploaded files is as easy as:

  1.  <?php
  2.   /**
  3.   * This represents just the file portion of the form processing
  4.   */
  5.   require_once('Upload.class.php');
  6.   require_once('MimeTypes.class.php');
  7.    
  8.   // set destination folder
  9.   $destination = '/path/to/destination/';
  10.    
  11.   // start new upload object
  12.   $ul = new AP_File_Upload();
  13.   
  14.   // loop through uploaded files and save each one
  15.   foreach($_FILES as $file)
  16.   {
  17.   if(!empty($file['name']) && $ul->save($file,$destination) == false)
  18.   {
  19.   // you'll want to handle error in some way
  20.   // more elegant than dying
  21.   die($ul->get_error());
  22.   }
  23.   }
  24.    
  25.   // continue processing files as needed

With the mime types class you can filter the kinds of files that are accepted by the script. For example you can only allow image file uploads by doing:

  1.  <?php
  2.   $m = new AP_File_MimeTypes();
  3.   $types = $m->getTypes('image');
  4.   
  5.   $ul = new AP_File_Upload($types);

Predefined groups in the class include: image, office, web, compressed, video, audio. If you look in the class you can see how easy it is to create your own groups. That could be a little more efficient if the MimeTypes class was static… but, well, it works and I’m lazy 😉 Even if you don’t filter the mime types you still need the MimeTypes class. The script uses it to check the file type that the upload reports vs. what the extension thinks it should be. Its not 100% accurate, but is OK for a first level check.

That’s all folks

The class makes handling uploads painfully easy. Just don’t let yourself be abused. Double checking files and altering the file names a good steps towards keeping your site safe. And if you’re dealing with files that you need to make available to others and that can potentially be used against you, you might consider zipping the contents of the file, unless you need to use it directly, then you’ve got other things to consider as well.

Update

2007-01-23 – fixed a line of code inside the foreach statement to correctly handle the submitted data. If using the multi-file script from the Stickman and uploading less than the limit of files, the first file in the $_FILES array is empty and thus threw a wrench in the works. I fixed the if statement to first check to make sure the name of a file is present. This gets around the issue that the 2nd and 3rd posters below were having.

Update 2

2007-08-30 – fixed the script to properly do file type checking. Embarrassingly enough I had written all the code needed but forgot to actually implement it in the script.

Update 3

2007-12-12 – disabled mime-type checker. Bah! Also, a change was made to the core functionality – the method save_file() has been renamed to save().

45 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  • Mmmmm.

    Tom, January 2, 2007 3:18 am | permalink

  • Hey,

    This code in conjunction with stickmans script were just what I was looking for in my administrator upload system. I do have one question that I was wondering if you could help me with. I created a little mockup using yours and stickmans code, and have been having trouble with the number of files uploaded. Specifically, if for example the max number of files is set to 5 and I upload 5 files, the system works fine, however if I try to upload less then 5 files, it doesn’t work at all. I was just wondering if you might know why its doing this. My code is included below:

    <html>
    
    <head>
        <!-- Include the javascript -->
        <script src="multifile_compressed.js"></script>
    </head>
    
    <body>
    
    <!-- This is the form -->
    <form enctype="multipart/form-data" action="uploadtest.php?action=upload" method = "post">
        <!-- The file element -- NOTE: it has an ID -->
        <input id="my_file_element" type="file" name="file_1" >
        <input type="submit">
    </form>
    Files:
    <!-- This is where the output will appear -->
    <div id="files_list"></div>
    <script>
        <!-- Create an instance of the multiSelector class, pass it the output target and the max number of files -->
        var multi_selector = new MultiSelector( document.getElementById( 'files_list' ), 3 );
        <!-- Pass in the file element -->
        multi_selector.addElement( document.getElementById( 'my_file_element' ) );
    </script>
    </body>
    </html>
    
    
    
    <?php
    /**
    * This represents just the file portion of the form processing
    */
    require_once('Upload.class.php');
    require_once('MimeTypes.class.php');
    
    if(isset($_GET['action'])) {
       $action = $_GET['action'];
    
       if($action == 'upload') {
          upload();
       }
    }
    
    
    function upload()
    {
       // set destination folder
       $destination = 'upload/';
    
       $m = new AP_File_MimeTypes();
       $types = $m->getTypes('image');
       unset($m);
    
       // start new upload object
       $ul = new AP_File_Upload($types);
    
       echo "hello";
    
       // loop through uploaded files and save each one
       foreach($_FILES as $file)
       {
          if($ul->save_file($file,$destination) == false)
          {
             // you'll want to handle error in some way
             // more elegant than dying
             die($ul->get_error());
           }
       }
    
        // continue processing files as needed
    }
    
    ?>
    

    Thanks for the code, and help

    Bryce, January 21, 2007 11:59 pm | permalink

  • First of all, a huge thank you for both you and stickman for this code. That is great of you both to provide your code for all of us. Unfortunately, I’m having the same problem as Bryce. If I upload the number of files that my max is set to, I have no problems. But if I upload any less than the max, none of the files upload. Any thoughts? Any help you can provide is much appreciated.

    Roni, January 23, 2007 12:00 pm | permalink

  • I finally got a minute to look at this and the error has been fixed above. I had forgotten a little quirk in the results of the script where if the limit wasn’t reached then there would be an empty item in the $_FILES array.

    Unfortunately, Roni & Bryce, you guys didn’t leave your email address so hopefully you check back to find this soon.

    Shawn Parker, January 23, 2007 12:30 pm | permalink

  • Ijust tryed these scripts and classes ant i have the same error as decribed above. My max is three files and i select only one or two, i get the message "No file uploaded. Please select a file to upload".

    I did not find where the test was done.

    Thanks for the code, thanks for help ?

    André, January 26, 2007 12:39 pm | permalink

  • Just confirm, you tried it with the revised code from the post and not with the code from Bryce?

    Bryce’s code will work by changing one line. Change line 61 from:

    if($ul->save_file($file,$destination) == false)

    to

    if(!empty($file['name']) && $ul->save_file($file,$destination) == false)

    Shawn Parker, January 27, 2007 5:43 pm | permalink

  • Wow, this script in connection with a multiple file-upload-script is very useful. thanks for that!

    mobilfunk, February 25, 2007 8:41 am | permalink

  • coluld you please provide a php4 solution. I tried removing the public/private stuff, managed to get it to not produce any errors but it does not actually upload any files 🙁 Worked fine on php5, but my client has php4. I can’t get the original script you posted on the stickman forum to work on 4 either.

    boggins, March 9, 2007 9:56 am | permalink

  • Boggins,

    Sorry, but its not likely. I haven’t worked on php4 in quite a while and I’m pretty bogged down (to use the obvious pun).

    I’ll see if I can make time for it though.

    As for the original script, not sure why it wouldn’t work, that’s code directly from a page I have in production now.

    Shawn Parker, March 10, 2007 12:50 pm | permalink

  • Shawn, thanks for the reply. Now sorted, nothing wrong with your script 😉

    boggins, March 12, 2007 2:32 am | permalink

  • When I try to use your Upload.Class.php, I’m getting a "Parse error: syntax error, unexpected T_STRING, expecting T_OLD_FUNCTION or T_FUNCTION or T_VAR or ‘}’ in /home/marvinj/public_html/TEST/upload/Upload.class.php on line 24" Thanks!

    Mike, March 22, 2007 10:40 am | permalink

  • Mike,

    It sounds like you’re trying to use the script with PHP4. The script is straight PHP5 with no backward compatibility.

    Shawn Parker, March 22, 2007 1:15 pm | permalink

  • This upload script has been so handy for my website. I have one problem/query though.

    The following lines of code work fine when I have files attached.

    if(!empty($file[‘name’]) && $ul->save_file ($file,$destination) == false )

    {

    $ErrorMessage = $ul->get_error();

    include ("Error.php");

    die();

    }

    I want to add a check that someone hasn’t hit the form submit button without actually attaching anything. A bit stupid on their part arguable, but I would like to alert people when they do so. It seems that if there are no files attached the code passes over the if statement.

    Any suggestions on how I can test if there are no files uploaded at all, not just ignore empty items in the $_FILES array?

    Cheers

    Peter, March 24, 2007 3:50 am | permalink

  • Peter, sorry for not answering earlier – its been a rough week.

    Because the $_FILES array will always have an member theres no way that I see as "clean" to do it, but that could just be me being fussy. What should work is putting something like this before you loop through the array to do the upload sequence:

    $r = current($_FILES);
    if(empty($r['name']))
    {
        // error
    }

    That will check to see if the first item in the $_FILES array is empty – if it is, then nothing was uploaded and you can stop the script before it wastes time iterating through the $_FILES array.

    Shawn Parker, April 1, 2007 10:04 am | permalink

  • hi, great code.

    how i can display the full path of the file when i call the object?

    foreach($_FILES as $file)

    {

    if(!empty($file[‘name’]) && $ul->save_file($file,$destination) == false)

    {

    // you’ll want to handle error in some way

    // more elegant than dying

    die($ul->get_error());

    }

    }

    yannick, April 2, 2007 9:17 am | permalink

  • Thanks so very much for this wonderful little script. I woke up this morning knowing I only had today to finish a site and still no clue how to upload multiple image files from one form. I’d done single file uploads before but couldn’t work out how to adapt the scripts.

    This little combination does the trick really well.

    David McNorton, April 7, 2007 5:02 am | permalink

  • I’m working with php5 and try your code but it seems that the MimeTypes.class.php doesn’t work at all with me?, I try the exact code without changing anything and when I upload a .css file is working, strange because the types setup is ‘image’ and in the groups ‘image’ doesn’t appear .css extension?

    thanks

    benoit, May 9, 2007 4:43 am | permalink

  • Hi,

    sorry me again..

    I’was just wondering if you know I to get back the files name when you use $new_name. because when I ask for $files[‘name’] appear the old name ?

    Thanks

    benoit, May 10, 2007 2:01 am | permalink

  • Benoit, I’ll have to take a look at that.

    Shawn Parker, May 13, 2007 11:19 pm | permalink

  • Greetings,

    I am using your original file upload script (using PHP 4) along with Stickman’s single file upload element with JavaScript, and everything is working perfectly. However, I have been trying to find a way to send these files to an email address instead of to a folder on the server. Is this possible?

    Thanks

    ponderit, May 15, 2007 10:47 am | permalink

  • Hello

    I am new to this and was wondering just how to get it working.

    1 where do i save the class files and with what name

    2 where and how do i call the class’s in stikmans script

    Bob Emert, May 16, 2007 1:00 pm | permalink

  • Ponderit: yes, there is. But you’ll have to write that part yourself. You’ll just need to grab the results of this script and use that as the source for attaching files to an email. Maybe I should post my email script… I quite like it for its simplicity.

    Bob Emerit: That’s a long topic in and of itself. You can store the class where every you’d like. You then get access to the script by including the class file in your script (or using autoload if you’ve set that up) and then off from there. You don’t call this class from Stickman’s script. His is client side Javascript for designating which photos to send to the server. This script processes those uploaded photos.

    Shawn Parker, May 16, 2007 5:14 pm | permalink

  • Benoit, to answer your second question (I still haven’t had a chance to look into the first):

    save_file() returns the path to the saved upload. So if you were to do something like:

    foreach($_FILES as $file)
    {
        if($s = $ul->save_file($file,$destination))
        {
            if($s !== false)
            {
                $uploaded[] = pathinfo($s);
            }
            else
            {
                // catch error
            }
        }
    }

    You’d have an array named $uploaded with information about your uploaded file(s).

    Maybe I should make that part of the example.

    Shawn Parker, May 16, 2007 5:20 pm | permalink

  • I’m having the same problem Benoit mentioned on 5/9. For some reason accepted file types aren’t being verified, even though I’m using your code as is and am trying to only allow for uploads of the ‘image’ group. Any ideas?

    Greg, May 17, 2007 1:13 pm | permalink

  • I appreciate your advice, although I am not a PHP developer. Do you know of anyone anywhere who has written a PHP to Email file upload solution that could be incorporated into your script? If not, how much would you charge to write it yourself?

    The form I’m putting together will not be accessible by the general public, it will only be used by one person and it will be password protected, so the risks should be next to 0.

    Thanks.

    ponderit, May 18, 2007 5:06 pm | permalink

  • Does anyone else know where I might find the code which would allow a web form to send files to an email as described in my previous post above?

    Thanks.

    ponderit, June 5, 2007 8:38 pm | permalink

  • Hi Shawn,

    Can you elaborate on this statement that you made on the 16th of May: "You’ll just need to grab the results of this script and use that as the source for attaching files to an email"? I am hitting one dead end after another in trying to find the solution.

    Thanks.

    ponderit, June 18, 2007 3:54 pm | permalink

  • Hi, for some reason the filter for the mime type doesn’t work in the example above. I copied the code you gave and then i tried to upload a php file and it let me.

    Any idea why this would happen?

    Mark, June 28, 2007 10:28 am | permalink

  • how to modify the class auto rename file based on date and time

    jason, July 2, 2007 6:38 pm | permalink

  • Jason, all the script does it replaces spaces with underscores. If you want to append that information you can do it manually during the save_file() command.

    For example:

    $ul->save_file($file,$destination,$new_name)

    You can supply anything that you’d like to change the name to in the $new_name variable. The script makes no attempt to make sure your file name matches the file type, so make sure you make that as part of the name.

    For the others having trouble with the mime-types

    I’m not ignoring you, I’m finally getting around to being able to fix this and will have the fix available soon. My apologies.

    Shawn Parker, July 3, 2007 3:27 pm | permalink

  • Well I used all the stickman and these files but they never upload…… What am I doing wrong! Anyone have a doc on how to set these 3 up?

    Big Merv, July 5, 2007 1:16 pm | permalink

  • I have a question: You use chmod() with octal values… I assume you are only hosting LAMP… I have to deal with IIS and Windows Server!

    Is there anything you would recommend to avoid problems with the chmod()?

    Dennis, August 30, 2007 6:26 pm | permalink

  • Okie-dokey! I don’t expect that any of you previous posters will be coming back, but I finally got time and updated the script to properly filter by file type. So, there it is.

    Dennis: You might consider altering line 155 (of the new script I just uploaded) and just taking out that check as there is no CHMOD on windows (as you probably already know). I don’t think you need to use any kind of permissions altering on Windows – it has been a long time since I’ve worked natively on Windows, so, I’m going to recommend this:

    Change:

    if(chmod($uploadfile,0755)) { return $uploadfile; }
    else
    {
        $this->_error = 'Could not change permissions on uploaded file. '.
                        'Please contact the system administrator';
        return false;
    }

    to just be

    if(is_file($uploadfile)) { return $uploadfile; }

    You really just need to make sure the permissions are set correctly on the target directory. The reason for doing this on *NIX is because of the file level permissions that are default. You don’t need to worry about that on Windows because the new file default is for it to be world readable and to have access controlled by the folder permissions. So you should be fine without it. I’ll look at some old code to see if I can confirm that, but that code is archived at work and I’m at home.

    ponderit: Email class is coming… and its been in production for close to a year now, so I know its good 😉 I just have to make it more general for public use by taking out some hooks specific to my site framework.

    Jason: not sure what you’re after there. Can you elaborate?

    Big Merv: Can you specify what kinds of errors you are getting? That would really help the troubleshooting.

    Shawn Parker, August 30, 2007 10:09 pm | permalink

  • hi their… i am a newbie in web programming, i have read all the comments that was posted, i am amaze of how it will be useful to my site… thnx to stick man who posted it and to the author/programmer who made this nice codes… thnx a lot. keep rockin dude

    tnchuntic, October 30, 2007 8:29 pm | permalink

  • hi. thanks so much for sharing this. i keep getting a ‘call to a member function save_file() on a non-object’ error on the file portion of the script. it’s referring to line 14:

    [code]if(!empty($file[‘name’]) && $ul->save_file($file,$destination) == false)[code]

    i actually cut and pasted from your post and only modified $destination. i’m not filtering mime types. both classes are installed and return no errors when viewed directly in a browser. any ideas? thanks.

    –brian

    Brian Gillett, November 8, 2007 10:12 pm | permalink

  • I’ve been struggling w/ this all day. I get an error when I upload a file and I can’t seem to find anything on it. I am sure I have PHP5 but maybe I am missing a required module or something?

    This is the error I get:

    Fatal error: Call to undefined method AP_File_Upload::save_file() in myserverpath/html/fileupload.php on line 17

    and here is line 17:

    if(!empty($file[‘name’]) && $ul->save_file($file,$destination) == false)

    Any ideas?

    Thanks,

    Tom

    Tom, December 1, 2007 6:45 pm | permalink

  • Ah, seems I need to update the example as I updated the class a bit recently.

    The call $ul->save_file($file,$destination) should be $ul->save($file,$destination).

    I’ll make that edit now.

    And, as many have probably noticed, the mime-type checker still has a but so its been completely disabled…

    Shawn Parker, December 12, 2007 9:19 pm | permalink

  • Hi,

    Thank you for writing the two classes. I have tried Bryce’s code since my form did not upload at all. I still am not able to upload. Could you suggest how I can fix the issue.

    Regards, –jh

    Jafar, January 21, 2008 11:11 am | permalink

  • sorry this might sound stupid but do you know how to disable mime-type checking?

    i’m trying to upload paperport max file and always had error: ‘file type does not match file extention’. i have to put

    ‘max’ => ‘unknown/unknown’ on array

    to make it works. thanks in advance

    devaryan, April 11, 2008 9:13 pm | permalink

  • Not sure if you read/use this blog anymore, but your code and stickman’s page have helped me a great deals. Thanks immensely for putting this online.

    I do have an oh so very small quessy…how do I implement a ‘loading.gif’ to be displayed when waiting for the file to be uploaded? Do I nee dto get into APC?

    Any direction would be good.

    Cheers

    Niz

    Niz, May 5, 2008 5:07 am | permalink

  • Yeah, I’ve neglected this for a bit – I’ve had so many other things going on as well but I should be able to wrap back around and fix a few things with this – mainly that mime-type checker pain in the ass.

    Niz: to get loading you can go about it two ways. If you just want a simple loader graphic that tells the users that its working then a little javascript should suffice. If you want a real progress bar that gives actual progress then you will need to look into something else. APC’s functionality in this regard is limited to PHP 5.2.5, however PERL can do the reporting needed to give a progress bar so that’s an option as well if your server doesn’t have PHP 5.2.5. I recently started playing with APC and I’m loving it. It doesn’t particularly fit within my normal form structure so I may need to rethink a few proceedural things to integrate it into my automated forms, but to just get it to work its pretty simple and I’m probably going to make a post on that within the next month. Though, there are already a few good ones out there – particularly an article on IBMs website: Linkie Poo

    shawn Parker, May 5, 2008 6:16 am | permalink

  • Thanks for getting back to me Shawn. The APC stuff does look good, and that link gives a superb explanation.

    Niz

    Niz, May 6, 2008 6:01 am | permalink

  • hi, thanks a lot for this code, i was wondering if you could update the entire code fixed

    please, i have to do this to a work from my school, but i can see that there is some troubles,

    thanks for all

    lluvia, May 20, 2008 1:45 pm | permalink

  • Hi,

    I’m having problems uploading mp3 files with the code. I’ve added all the alternative mime types for mp3’s and still no luck. Any suggestions? Thanks in advance

    Ubest, July 27, 2008 11:15 am | permalink

  • Hi,

    Very new to this but trying to give it a go…

    I don’t seem to be able to save the "upload class" and "mime-type" class files from above

    I’ve used right click and "save target as" but keep getting error.

    Have they been removed? If so perhaps someone could help me out with where to find them?

    Thanks

    Chris

    Chris Spencer, January 8, 2009 11:13 am | permalink

Comments are closed