May 25 2009

Upload Multiple Images at Once using PHP

PHP and FancyUploadUsing PHP and FancyUpload you can select multiple files to upload at once. FancyUpload is a powerful and elegant Ajax file uploading script that is easy to setup and configure. What makes FancyUpload attractive to use for uploading files is that it’s server independent and completely styliable using CSS and XHTML, plus it uses MooTools to function across modern browsers.

The purpose of this tutorial is to show you how to use FancyUpload for uploading multiple images at once using PHP. Please note that FancyUpload is able to upload other file types as well. I recommend you visit their website to see other showcases.



About PHP File Upload
When posting form inputs of “file” type, PHP uses the global $_FILES array to upload a file from a client computer to the remote server. The $_FILES array contains the following parameters:

1
2
3
4
5
$_FILES["file"]["tmp_name"] // A temporary name designated by the server
$_FILES["file"]["name"] // Original filename
$_FILES["file"]["type"] // Mime-type, in this case "image-type"
$_FILES["file"]["size"] // File size in bytes
$_FILES["file"]["error"] // Error code information about failed upload



File Upload and Security
It is important to use an upload mechanism that prevents malicious users from performing any unauthorized task. The code below uses the move_uploaded_file($filename, $destination) function to ensure that the file designated by $filename is a valid upload file (meaning that it was uploaded via PHP’s HTTP POST upload mechanism).

If the file is valid, it will be moved to the $filename given by the $destination parameter. However, if $filename is not a valid upload file, then no action will occur, and move_uploaded_file() will return FALSE. If $filename is a valid upload file, but cannot be moved for some reason, no action will occur, and move_uploaded_file() will return FALSE. Additionally, a warning will be issued.

This sort of check is especially important if there is any chance that anything done with uploaded files could reveal their contents to the user, or even to other users on the same system.

1
2
3
4
5
6
7
8
9
10
11
12
13
/***BOF FILE UPLOAD***/

// Check that the images directory exists
if(file_exists($upload_directory))
{          
    // Upload file or return error message information
    if(!move_uploaded_file($_FILES['Filedata']['tmp_name'], $upload_directory .$_FILES['Filedata']['name']))
    {
        $error  = "Could not upload file " .$_FILES["Filedata"]["name"] ."<br />" .$_FILES;
    }
}
       
/***EOF FILE UPLOAD***/

See Demo



Before you Begin
Download the complete ActionScript and JavaScript source files, documentation and showcases from the FancyUpload website. The source code below is part of the “Photo Queue” showcase which is intended for uploading images only by restricting the file type.

Using FileZilla or your favorite FTP client, upload the decompressed “fancyupload” directory to your web server. Next, create a directory where you want to upload images and assign it file permissions 777. Now let’s begin!

FancyUpload version 3.0 features:
- Ability to select multiple files to upload at once
- Filter file by type
- A lot of possible Events to add your own behaviour
- Show and filter useful file information before the upload starts
- Limit uploads by file count, type or size
- Platform and server independent, just needs Flash 9+ (> 95% penetration)
- Graceful Degradation, since the element is replaced after the Flash is loaded successfully
- Cancel running uploads, add files during upload
- Everything is optional, documented and easy editable

Compatibility
Fully compatible with all A-Grade Browsers (Internet Explorer 6+, Opera 9, Firefox 1.5+ and Safari 3+) with Adobe Flash 9 and 10 player.


Source Files:

CSS Stylesheet
/fancyupload/showcase/photoqueue/style.css

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/**
 * FancyUpload Showcase
 *
 * @license     MIT License
 * @author      Harald Kirschner <mail [at] digitarald [dot] de>
 * @copyright   Authors
 */

 
/* CSS vs. Adblock tabs */
.swiff-uploader-box a {
    display: none !important;
}
 
/* .hover simulates the flash interactions */
a:hover, a.hover {
    color: red;
}
 
#demo-status {
    padding: 10px 15px;
    width: 420px;
    border: 1px solid #eee;
}
 
#demo-status .progress {
    background: url(../../assets/progress-bar/progress.gif) no-repeat;
    background-position: +50% 0;
    margin-right: 0.5em;
    vertical-align: middle;
}
 
#demo-status .progress-text {
    font-size: 0.9em;
    font-weight: bold;
}
 
#demo-list {
    list-style: none;
    width: 450px;
    margin: 0;
}
 
#demo-list li.validation-error {
    padding-left: 44px;
    display: block;
    clear: left;
    line-height: 40px;
    color: #8a1f11;
    cursor: pointer;
    border-bottom: 1px solid #fbc2c4;
    background: #fbe3e4 url(assets/failed.png) no-repeat 4px 4px;
}
 
#demo-list li.file {
    border-bottom: 1px solid #eee;
    background: url(assets/file.png) no-repeat 4px 4px;
    overflow: auto;
}
#demo-list li.file.file-uploading {
    background-image: url(assets/uploading.png);
    background-color: #D9DDE9;
}
#demo-list li.file.file-success {
    background-image: url(assets/success.png);
}
#demo-list li.file.file-failed {
    background-image: url(assets/failed.png);
}
 
#demo-list li.file .file-name {
    font-size: 1.2em;
    margin-left: 44px;
    display: block;
    clear: left;
    line-height: 40px;
    height: 40px;
    font-weight: bold;
}
#demo-list li.file .file-size {
    font-size: 0.9em;
    line-height: 18px;
    float: right;
    margin-top: 2px;
    margin-right: 6px;
}
#demo-list li.file .file-info {
    display: block;
    margin-left: 44px;
    font-size: 0.9em;
    line-height: 20px;
    clear
}
#demo-list li.file .file-remove {
    clear: right;
    float: right;
    line-height: 18px;
    margin-right: 6px;
}

Javascript and MooTools
/fancyupload/showcase/photoqueue/script.js

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
 * FancyUpload Showcase
 *
 * @license     MIT License
 * @author      Harald Kirschner <mail [at] digitarald [dot] de>
 * @copyright   Authors
 */

 
window.addEvent('domready', function() { // wait for the content
 
    // our uploader instance
 
    var up = new FancyUpload2($('demo-status'), $('demo-list'), { // options object
        // we console.log infos, remove that in production!!
        verbose: true,
 
        // url is read from the form, so you just have to change one place
        url: $('form-demo').action,
 
        // path to the SWF file
        path: '../../source/Swiff.Uploader.swf',
 
        // remove that line to select all files, or edit it, add more items
        typeFilter: {
            'Images (*.jpg, *.jpeg, *.gif, *.png)': '*.jpg; *.jpeg; *.gif; *.png'
        },
 
        // this is our browse button, *target* is overlayed with the Flash movie
        target: 'demo-browse',
 
        // graceful degradation, onLoad is only called if all went well with Flash
        onLoad: function() {
            $('demo-status').removeClass('hide'); // we show the actual UI
            $('demo-fallback').destroy(); // ... and hide the plain form
 
            // We relay the interactions with the overlayed flash to the link
            this.target.addEvents({
                click: function() {
                    return false;
                },
                mouseenter: function() {
                    this.addClass('hover');
                },
                mouseleave: function() {
                    this.removeClass('hover');
                    this.blur();
                },
                mousedown: function() {
                    this.focus();
                }
            });
 
            // Interactions for the 2 other buttons
 
            $('demo-clear').addEvent('click', function() {
                up.remove(); // remove all files
                return false;
            });
 
            $('demo-upload').addEvent('click', function() {
                up.start(); // start upload
                return false;
            });
        },
 
        // Edit the following lines, it is your custom event handling
 
        /**
         * Is called when files were not added, "files" is an array of invalid File classes.
         *
         * This example creates a list of error elements directly in the file list, which
         * hide on click.
         */

        onSelectFail: function(files) {
            files.each(function(file) {
                new Element('li', {
                    'class': 'validation-error',
                    html: file.validationErrorMessage || file.validationError,
                    title: MooTools.lang.get('FancyUpload', 'removeTitle'),
                    events: {
                        click: function() {
                            this.destroy();
                        }
                    }
                }).inject(this.list, 'top');
            }, this);
        },
 
        /**
         * This one was directly in FancyUpload2 before, the event makes it
         * easier for you, to add your own response handling (you probably want
         * to send something else than JSON or different items).
         */

        onFileSuccess: function(file, response) {
            var json = new Hash(JSON.decode(response, true) || {});
 
            if (json.get('status') == '1') {
                file.element.addClass('file-success');
                file.info.set('html', '<strong>Image was uploaded:</strong> ' + json.get('width') + ' x ' + json.get('height') + 'px, <em>' + json.get('mime') + '</em>)');
            } else {
                file.element.addClass('file-failed');
                file.info.set('html', '<strong>An error occured:</strong> ' + (json.get('error') ? (json.get('error') + ' #' + json.get('code')) : response));
            }
        },
 
        /**
         * onFail is called when the Flash movie got bashed by some browser plugin
         * like Adblock or Flashblock.
         */

        onFail: function(error) {
            switch (error) {
                case 'hidden': // works after enabling the movie and clicking refresh
                    alert('To enable the embedded uploader, unblock it in your browser and refresh (see Adblock).');
                    break;
                case 'blocked': // This no *full* fail, it works after the user clicks the button
                    alert('To enable the embedded uploader, enable the blocked Flash movie (see Flashblock).');
                    break;
                case 'empty': // Oh oh, wrong path
                    alert('A required file was not found, please be patient and we fix this.');
                    break;
                case 'flash': // no flash 9+ :(
                    alert('To enable the embedded uploader, install the latest Adobe Flash plugin.')
            }
        }
 
    });
 
});

PHP Image Upload Script
/fancyupload/showcase/script.php
Note: This script has been modified from its original form and I’ve commented out lines 39 through 58 to disable the showcase’s test mode and implemented code for production mode.

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/**
     * Swiff.Uploader Example Backend
     *
     * This file represents a simple logging, validation and output.
     *  *
     * WARNING: If you really copy these lines in your backend without
     * any modification, there is something seriously wrong! Drop me a line
     * and I can give you a good rate for fancy and customised installation.
     *
     * No showcase represents 100% an actual real world file handling,
     * you need to move and process the file in your own code!
     * Just like you would do it with other uploaded files, nothing
     * special.
     *
     * @license     MIT License
     *
     * @author      Harald Kirschner <mail [at] digitarald [dot] de>
     * @copyright   Authors
     *
     */

   
   
    /**
     * Only needed if you have a logged in user, see option appendCookieData,
     * which adds session id and other available cookies to the sent data.
     *
     * session_id($_POST['SID']); // whatever your session name is, adapt that!
     * session_start();
     */

   
    // Request log
   
    /**
     * You don't need to log, this is just for the showcase. Better remove
     * those lines for production since the log contains detailed file
     * information.
     */

   
    /*$result = array();
   
    $result['time']  = date('r');
    $result['addr']  = substr_replace(gethostbyaddr($_SERVER['REMOTE_ADDR']), '******', 0, 6);
    $result['agent'] = $_SERVER['HTTP_USER_AGENT'];
   
    if(count($_GET))   { $result['get'] = $_GET; }
    if(count($_POST))  { $result['post'] = $_POST; }
    if(count($_FILES)) { $result['files'] = $_FILES; }
   
    // we kill an old file to keep the size small
    if(file_exists('script.log') && filesize('script.log') > 102400) { unlink('script.log'); }
   
    $log = @fopen('script.log', 'a');
   
    if($log)
    {
        fputs($log, print_r($result, true) . "\n---\n");
        fclose($log);
    }*/

   
   
    // Validation
   
    $error = false;
       
       // NOTE: You need to create a directory if it doesn't already exist
       // Specify directory path for your images directory
    $upload_directory  = $_SERVER['DOCUMENT_ROOT'] ."/fancyupload/uploads/";
   
    if(!isset($_FILES['Filedata']) || !is_uploaded_file($_FILES['Filedata']['tmp_name'])) { $error = 'Invalid Upload'; }
   
    /**
     * You would add more validation, checking image type or user rights.
     *
     */

   
    if(!$error && $_FILES['Filedata']['size'] > 2 * 1024 * 1024) { $error = 'Please upload only files smaller than 2Mb!'; }
    else if(!$error && !($size = @getimagesize($_FILES['Filedata']['tmp_name']))) { $error = 'Please upload only images, no other files are supported.'; }
    else if(!$error && !in_array($size[2], array(1, 2, 3, 7, 8))) { $error = 'Please upload only images of type JPEG, GIF or PNG.'; }
    else if(!$error && ($size[0] < 25) || ($size[1] < 25)) { $error = 'Please upload an image bigger than 25px.'; }
    else if(!$error && file_exists($upload_directory .$_FILES['Filedata']['tmp_name'])) { $error = $_FILES["Filedata"]["name"] ." already exists."; }  
   
   
    // Processing
   
    /**
     * Its a demo, you would move or process the file like:
     *
     * move_uploaded_file($_FILES['Filedata']['tmp_name'], '../uploads/' . $_FILES['Filedata']['name']);
     * $return['src'] = '/uploads/' . $_FILES['Filedata']['name'];
     *
     * or
     *
     * $return['link'] = YourImageLibrary::createThumbnail($_FILES['Filedata']['tmp_name']);
     *
     */

   
    if($error) { $return = array('status' => '0', 'error' => $error); }
   
    else
    {
        $return = array('status' => '1', 'name' => $_FILES['Filedata']['name']);
   
        // Our processing, we get a hash value from the file
        $return['hash'] = md5_file($_FILES['Filedata']['tmp_name']);
   
        // ... and if available, we get image data
        $info = @getimagesize($_FILES['Filedata']['tmp_name']);
       
        /***BOF FILE UPLOAD***/
       
        // Specify directory path for your images directory
        $upload_directory  = $_SERVER['DOCUMENT_ROOT'] ."/fancyupload/images/";
       
        // Check that the images directory exists
        if(file_exists($upload_directory))
        {          
            // Upload file or return error message information
            if(!move_uploaded_file($_FILES['Filedata']['tmp_name'], $upload_directory .$_FILES['Filedata']['name']))
            {
                $error  = "Could not upload file " .$_FILES["Filedata"]["name"] ."<br />" .$_FILES;
            }
        }
       
        /***EOF FILE UPLOAD***/
   
        if($info)
        {
            $return['width']  = $info[0];
            $return['height'] = $info[1];
            $return['mime']   = $info['mime'];
        }
   
    }
   
   
    // Output
   
    /**
     * Again, a demo case. We can switch here, for different showcases
     * between different formats. You can also return plain data, like an URL
     * or whatever you want.
     *
     * The Content-type headers are uncommented, since Flash doesn't care for them
     * anyway. This way also the IFrame-based uploader sees the content.
     */

   
    if (isset($_REQUEST['response']) && $_REQUEST['response'] == 'xml')
    {
        // header('Content-type: text/xml');
   
        // Really dirty, use DOM and CDATA section!
        echo '<response>';
        foreach ($return as $key => $value) { echo "<$key><![CDATA[$value]]></$key>"; }
        echo '</response>';
    }
   
    else
    {
        // header('Content-type: application/json');
        echo json_encode($return);
    }

XHTML Markup
/fancyupload/showcase/photoqueue/index.html

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
<form action="../script.php" method="post" enctype="multipart/form-data" id="form-demo">
 
    <fieldset id="demo-fallback">
        <legend>File Upload</legend>
        <p>
            This form is just an example fallback for the unobtrusive behaviour of FancyUpload.
            If this part is not changed, something must be wrong with your code.
        </p>
        <label for="demo-photoupload">
            Upload a Photo:
            <input type="file" name="Filedata" />
        </label>
    </fieldset>
 
    <div id="demo-status" class="hide">
        <p>
            <a href="#" id="demo-browse">Browse Files</a> |
            <a href="#" id="demo-clear">Clear List</a> |
            <a href="#" id="demo-upload">Start Upload</a>
        </p>
        <div>
            <strong class="overall-title"></strong><br />
            <img src="../../assets/progress-bar/bar.gif" class="progress overall-progress" />
        </div>
        <div>
            <strong class="current-title"></strong><br />
            <img src="../../assets/progress-bar/bar.gif" class="progress current-progress" />
        </div>
        <div class="current-text"></div>
    </div>
 
    <ul id="demo-list"></ul>
 
</form>

Note that the “enctype” attribute for the form specifies the content-type to use when submitting the form. The “multipart/form-data” is used when a form requires binary data, like the contents of a file, to be uploaded.

Putting it all together
Modify /fancyupload/showcase/photoqueue/index.html to include all Javascript and CSS files necessary for FancyUpload to work properly.

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>FancyUpload Photo Queue Showcase</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<link href="style.css" rel="stylesheet" media="screen" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.2.2/mootools.js"></script>
<script type="text/javascript" src="../../source/Swiff.Uploader.js"></script>
<script type="text/javascript" src="../../source/Fx.ProgressBar.js"></script>
<script type="text/javascript" src="../../source/Lang.js"></script>
<script type="text/javascript" src="../../source/FancyUpload2.js"></script>
<script type="text/javascript" src="script.js"></script>
<style type="text/css">
 body {
    font-family:Verdana, Arial, Helvetica, sans-serif;
    font-size:11px;
 }
</style>
</head>
<body>
 <br />
 <div style="width:500px; margin-left:auto; margin-right:auto;">
 <h3>Upload Multiple Images at Once using PHP and FancyUpload</h3>
 <form action="../script.php" method="post" enctype="multipart/form-data" id="form-demo">

    <fieldset id="demo-fallback">
        <legend>File Upload</legend>
        <p>
            This form is just an example fallback for the unobtrusive behaviour of FancyUpload.
            If this part is not changed, something must be wrong with your code.
        </p>
        <label for="demo-photoupload">
            Upload a Photo:
            <input type="file" name="Filedata" />
        </label>
    </fieldset>

    <div id="demo-status" class="hide">
   <p>
    <a href="#" id="demo-browse">Browse Files</a> &nbsp; | &nbsp;
    <a href="#" id="demo-clear">Clear List</a> &nbsp; | &nbsp;
    <a href="#" id="demo-upload">Start Upload</a>
   </p>
        <div>
            <strong class="overall-title"></strong><br />
            <img src="../../assets/progress-bar/bar.gif" class="progress overall-progress" />
        </div>
        <div>
            <strong class="current-title"></strong><br />
            <img src="../../assets/progress-bar/bar.gif" class="progress current-progress" />
        </div>
        <div class="current-text"></div>
    </div>

    <ul id="demo-list"></ul>

</form>
 </div>
</body>
</html>

Note: You will need to include the MooTools.Lang.js Javascript for FancyUpload to work properly. For my installation I chose to put the file in /fancyupload/source/Lang.js to maintain consistency by keeping all source files in the same directory.

FancyUpload is a creation of Harald Kirschner and is available free under the MIT License.

May 25th by Hektor

Leave a Reply