Upload Multiple Images at Once using PHP
Using 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***/ |
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> | <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> </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.