├── config └── settings.php ├── libraries └── upload.class.php ├── public ├── css │ ├── jquery.fileupload-ui.css │ └── style.css ├── img │ ├── loading.gif │ └── progressbar.gif ├── js │ ├── cors │ │ ├── jquery.postmessage-transport.js │ │ └── jquery.xdr-transport.js │ ├── jquery.fileupload-fp.js │ ├── jquery.fileupload-ui.js │ ├── jquery.fileupload.js │ ├── jquery.iframe-transport.js │ ├── locale.js │ ├── main.js │ ├── test.js │ └── vendor │ │ └── jquery.ui.widget.js └── uploads │ ├── files │ └── .gitkeep │ └── thumbnails │ └── .gitkeep ├── readme.md ├── routes.php ├── start.php └── views ├── index.php └── test.php /config/settings.php: -------------------------------------------------------------------------------- 1 | URL::to_route('upload'), 5 | 'upload_dir' => path('public').'bundles/jupload/uploads/files/', 6 | 'upload_url' => URL::base().'/bundles/jupload/uploads/files/', 7 | 'delete_type' => 'POST', 8 | 'image_versions' => array( 9 | 'thumbnail' => array( 10 | 'upload_dir' => path('public').'bundles/jupload/uploads/thumbnails/', 11 | 'upload_url' => URL::base().'/bundles/jupload/uploads/thumbnails/', 12 | ), 13 | ), 14 | ); -------------------------------------------------------------------------------- /libraries/upload.class.php: -------------------------------------------------------------------------------- 1 | options = array( 19 | 'script_url' => $this->getFullUrl().'/', 20 | 'upload_dir' => dirname($_SERVER['SCRIPT_FILENAME']).'/files/', 21 | 'upload_url' => $this->getFullUrl().'/files/', 22 | 'param_name' => 'files', 23 | // Set the following option to 'POST', if your server does not support 24 | // DELETE requests. This is a parameter sent to the client: 25 | 'delete_type' => 'DELETE', 26 | // The php.ini settings upload_max_filesize and post_max_size 27 | // take precedence over the following max_file_size setting: 28 | 'max_file_size' => null, 29 | 'min_file_size' => 1, 30 | 'accept_file_types' => '/.+$/i', 31 | // The maximum number of files for the upload directory: 32 | 'max_number_of_files' => null, 33 | // Image resolution restrictions: 34 | 'max_width' => null, 35 | 'max_height' => null, 36 | 'min_width' => 1, 37 | 'min_height' => 1, 38 | // Set the following option to false to enable resumable uploads: 39 | 'discard_aborted_uploads' => true, 40 | // Set to true to rotate images based on EXIF meta data, if available: 41 | 'orient_image' => false, 42 | 'image_versions' => array( 43 | // Uncomment the following version to restrict the size of 44 | // uploaded images. You can also add additional versions with 45 | // their own upload directories: 46 | /* 47 | 'large' => array( 48 | 'upload_dir' => dirname($_SERVER['SCRIPT_FILENAME']).'/files/', 49 | 'upload_url' => $this->getFullUrl().'/files/', 50 | 'max_width' => 1920, 51 | 'max_height' => 1200, 52 | 'jpeg_quality' => 95 53 | ), 54 | */ 55 | 'thumbnail' => array( 56 | 'upload_dir' => dirname($_SERVER['SCRIPT_FILENAME']).'/thumbnails/', 57 | 'upload_url' => $this->getFullUrl().'/thumbnails/', 58 | 'max_width' => 80, 59 | 'max_height' => 80 60 | ) 61 | ) 62 | ); 63 | if ($options) { 64 | $this->options = array_replace_recursive($this->options, $options); 65 | } 66 | } 67 | 68 | protected function getFullUrl() { 69 | $https = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'; 70 | return 71 | ($https ? 'https://' : 'http://'). 72 | (!empty($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'].'@' : ''). 73 | (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ($_SERVER['SERVER_NAME']. 74 | ($https && $_SERVER['SERVER_PORT'] === 443 || 75 | $_SERVER['SERVER_PORT'] === 80 ? '' : ':'.$_SERVER['SERVER_PORT']))). 76 | substr($_SERVER['SCRIPT_NAME'],0, strrpos($_SERVER['SCRIPT_NAME'], '/')); 77 | } 78 | 79 | protected function set_file_delete_url($folder, $file) { 80 | $file->delete_url = $this->options['script_url'].'/'.$folder 81 | .'?file='.rawurlencode($file->name); 82 | $file->delete_type = $this->options['delete_type']; 83 | if ($file->delete_type !== 'DELETE') { 84 | $file->delete_url .= '&_method=DELETE'; 85 | } 86 | } 87 | 88 | protected function get_file_object($folder, $file_name) { 89 | $file_path = $this->options['upload_dir'].$folder.$file_name; 90 | if (is_file($file_path) && $file_name[0] !== '.') { 91 | $file = new stdClass(); 92 | $file->name = $file_name; 93 | $file->size = filesize($file_path); 94 | $file->url = $this->options['upload_url'].$folder.rawurlencode($file->name); 95 | foreach($this->options['image_versions'] as $version => $options) { 96 | if (is_file($options['upload_dir'].$folder.$file_name)) { 97 | $file->{$version.'_url'} = $options['upload_url'] 98 | .$folder.rawurlencode($file->name); 99 | } 100 | } 101 | $this->set_file_delete_url($folder, $file); 102 | return $file; 103 | } 104 | return null; 105 | } 106 | 107 | protected function get_file_objects($folder) { 108 | $dates = scandir($this->options['upload_dir'].$folder); 109 | $folders = array_fill(1, sizeof($dates), $folder); 110 | return array_values(array_filter(array_map( 111 | array($this, 'get_file_object'),$folders, 112 | $dates 113 | ))); 114 | } 115 | 116 | protected function create_scaled_image($folder, $file_name, $options) { 117 | $file_path = $this->options['upload_dir'].$folder.$file_name; 118 | $new_file_path = $options['upload_dir'].$folder.$file_name; 119 | list($img_width, $img_height) = @getimagesize($file_path); 120 | if (!$img_width || !$img_height) { 121 | return false; 122 | } 123 | $scale = min( 124 | $options['max_width'] / $img_width, 125 | $options['max_height'] / $img_height 126 | ); 127 | if ($scale >= 1) { 128 | if ($file_path !== $new_file_path) { 129 | return copy($file_path, $new_file_path); 130 | } 131 | return true; 132 | } 133 | $new_width = $img_width * $scale; 134 | $new_height = $img_height * $scale; 135 | $new_img = @imagecreatetruecolor($new_width, $new_height); 136 | switch (strtolower(substr(strrchr($file_name, '.'), 1))) { 137 | case 'jpg': 138 | case 'jpeg': 139 | $src_img = @imagecreatefromjpeg($file_path); 140 | $write_image = 'imagejpeg'; 141 | $image_quality = isset($options['jpeg_quality']) ? 142 | $options['jpeg_quality'] : 75; 143 | break; 144 | case 'gif': 145 | @imagecolortransparent($new_img, @imagecolorallocate($new_img, 0, 0, 0)); 146 | $src_img = @imagecreatefromgif($file_path); 147 | $write_image = 'imagegif'; 148 | $image_quality = null; 149 | break; 150 | case 'png': 151 | @imagecolortransparent($new_img, @imagecolorallocate($new_img, 0, 0, 0)); 152 | @imagealphablending($new_img, false); 153 | @imagesavealpha($new_img, true); 154 | $src_img = @imagecreatefrompng($file_path); 155 | $write_image = 'imagepng'; 156 | $image_quality = isset($options['png_quality']) ? 157 | $options['png_quality'] : 9; 158 | break; 159 | default: 160 | $src_img = null; 161 | } 162 | $success = $src_img && @imagecopyresampled( 163 | $new_img, 164 | $src_img, 165 | 0, 0, 0, 0, 166 | $new_width, 167 | $new_height, 168 | $img_width, 169 | $img_height 170 | ) && $write_image($new_img, $new_file_path, $image_quality); 171 | // Free up memory (imagedestroy does not delete files): 172 | @imagedestroy($src_img); 173 | @imagedestroy($new_img); 174 | return $success; 175 | } 176 | 177 | protected function validate($uploaded_file, $file, $error, $index) { 178 | if ($error) { 179 | $file->error = $error; 180 | return false; 181 | } 182 | if (!$file->name) { 183 | $file->error = 'missingFileName'; 184 | return false; 185 | } 186 | if (!preg_match($this->options['accept_file_types'], $file->name)) { 187 | $file->error = 'acceptFileTypes'; 188 | return false; 189 | } 190 | if ($uploaded_file && is_uploaded_file($uploaded_file)) { 191 | $file_size = filesize($uploaded_file); 192 | } else { 193 | $file_size = $_SERVER['CONTENT_LENGTH']; 194 | } 195 | if ($this->options['max_file_size'] && ( 196 | $file_size > $this->options['max_file_size'] || 197 | $file->size > $this->options['max_file_size']) 198 | ) { 199 | $file->error = 'maxFileSize'; 200 | return false; 201 | } 202 | if ($this->options['min_file_size'] && 203 | $file_size < $this->options['min_file_size']) { 204 | $file->error = 'minFileSize'; 205 | return false; 206 | } 207 | if (is_int($this->options['max_number_of_files']) && ( 208 | count($this->get_file_objects()) >= $this->options['max_number_of_files']) 209 | ) { 210 | $file->error = 'maxNumberOfFiles'; 211 | return false; 212 | } 213 | list($img_width, $img_height) = @getimagesize($uploaded_file); 214 | if (is_int($img_width)) { 215 | if ($this->options['max_width'] && $img_width > $this->options['max_width'] || 216 | $this->options['max_height'] && $img_height > $this->options['max_height']) { 217 | $file->error = 'maxResolution'; 218 | return false; 219 | } 220 | if ($this->options['min_width'] && $img_width < $this->options['min_width'] || 221 | $this->options['min_height'] && $img_height < $this->options['min_height']) { 222 | $file->error = 'minResolution'; 223 | return false; 224 | } 225 | } 226 | return true; 227 | } 228 | 229 | protected function upcount_name_callback($matches) { 230 | $index = isset($matches[1]) ? intval($matches[1]) + 1 : 1; 231 | $ext = isset($matches[2]) ? $matches[2] : ''; 232 | return ' ('.$index.')'.$ext; 233 | } 234 | 235 | protected function upcount_name($name) { 236 | return preg_replace_callback( 237 | '/(?:(?: \(([\d]+)\))?(\.[^.]+))?$/', 238 | array($this, 'upcount_name_callback'), 239 | $name, 240 | 1 241 | ); 242 | } 243 | 244 | protected function trim_file_name($name, $type, $index) { 245 | // Remove path information and dots around the filename, to prevent uploading 246 | // into different directories or replacing hidden system files. 247 | // Also remove control characters and spaces (\x00..\x20) around the filename: 248 | $file_name = trim(basename(stripslashes($name)), ".\x00..\x20"); 249 | // Add missing file extension for known image types: 250 | if (strpos($file_name, '.') === false && 251 | preg_match('/^image\/(gif|jpe?g|png)/', $type, $matches)) { 252 | $file_name .= '.'.$matches[1]; 253 | } 254 | if ($this->options['discard_aborted_uploads']) { 255 | while(is_file($this->options['upload_dir'].$file_name)) { 256 | $file_name = $this->upcount_name($file_name); 257 | } 258 | } 259 | return $file_name; 260 | } 261 | 262 | protected function handle_form_data($file, $index) { 263 | // Handle form data, e.g. $_REQUEST['description'][$index] 264 | } 265 | 266 | protected function orient_image($file_path) { 267 | $exif = @exif_read_data($file_path); 268 | if ($exif === false) { 269 | return false; 270 | } 271 | $orientation = intval(@$exif['Orientation']); 272 | if (!in_array($orientation, array(3, 6, 8))) { 273 | return false; 274 | } 275 | $image = @imagecreatefromjpeg($file_path); 276 | switch ($orientation) { 277 | case 3: 278 | $image = @imagerotate($image, 180, 0); 279 | break; 280 | case 6: 281 | $image = @imagerotate($image, 270, 0); 282 | break; 283 | case 8: 284 | $image = @imagerotate($image, 90, 0); 285 | break; 286 | default: 287 | return false; 288 | } 289 | $success = imagejpeg($image, $file_path); 290 | // Free up memory (imagedestroy does not delete files): 291 | @imagedestroy($image); 292 | return $success; 293 | } 294 | 295 | protected function handle_file_upload($folder, $uploaded_file, $name, $size, $type, $error, $index = null) { 296 | $file = new stdClass(); 297 | $file->name = $this->trim_file_name($name, $type, $index); 298 | $file->size = intval($size); 299 | $file->type = $type; 300 | if ($this->validate($uploaded_file, $file, $error, $index)) { 301 | $this->handle_form_data($file, $index); 302 | $file_path = $this->options['upload_dir'].$folder.$file->name; 303 | $append_file = !$this->options['discard_aborted_uploads'] && 304 | is_file($file_path) && $file->size > filesize($file_path); 305 | clearstatcache(); 306 | if ($uploaded_file && is_uploaded_file($uploaded_file)) { 307 | // multipart/formdata uploads (POST method uploads) 308 | if ($append_file) { 309 | file_put_contents( 310 | $file_path, 311 | fopen($uploaded_file, 'r'), 312 | FILE_APPEND 313 | ); 314 | } else { 315 | move_uploaded_file($uploaded_file, $file_path); 316 | } 317 | } else { 318 | // Non-multipart uploads (PUT method support) 319 | file_put_contents( 320 | $file_path, 321 | fopen('php://input', 'r'), 322 | $append_file ? FILE_APPEND : 0 323 | ); 324 | } 325 | $file_size = filesize($file_path); 326 | if ($file_size === $file->size) { 327 | if ($this->options['orient_image']) { 328 | $this->orient_image($file_path); 329 | } 330 | $file->url = $this->options['upload_url'].$folder.rawurlencode($file->name); 331 | foreach($this->options['image_versions'] as $version => $options) { 332 | if ($this->create_scaled_image($folder,$file->name, $options)) { 333 | if ($this->options['upload_dir'] !== $options['upload_dir']) { 334 | $file->{$version.'_url'} = $options['upload_url'].$folder 335 | .rawurlencode($file->name); 336 | } else { 337 | clearstatcache(); 338 | $file_size = filesize($file_path); 339 | } 340 | } 341 | } 342 | } else if ($this->options['discard_aborted_uploads']) { 343 | unlink($file_path); 344 | $file->error = 'abort'; 345 | } 346 | $file->size = $file_size; 347 | $this->set_file_delete_url($folder, $file); 348 | } 349 | return $file; 350 | } 351 | 352 | public function get($folder) { 353 | $file_name = isset($_REQUEST['file']) ? 354 | basename(stripslashes($_REQUEST['file'])) : null; 355 | if ($file_name) { 356 | $info = $this->get_file_object($folder, $file_name); 357 | } else { 358 | $info = $this->get_file_objects($folder); 359 | } 360 | header('Content-type: application/json'); 361 | echo json_encode($info); 362 | } 363 | 364 | public function post($folder) { 365 | if (isset($_REQUEST['_method']) && $_REQUEST['_method'] === 'DELETE') { 366 | return $this->delete($folder); 367 | } 368 | $upload = isset($_FILES[$this->options['param_name']]) ? 369 | $_FILES[$this->options['param_name']] : null; 370 | $info = array(); 371 | if ($upload && is_array($upload['tmp_name'])) { 372 | // param_name is an array identifier like "files[]", 373 | // $_FILES is a multi-dimensional array: 374 | foreach ($upload['tmp_name'] as $index => $value) { 375 | $info[] = $this->handle_file_upload($folder, 376 | $upload['tmp_name'][$index], 377 | isset($_SERVER['HTTP_X_FILE_NAME']) ? 378 | $_SERVER['HTTP_X_FILE_NAME'] : $upload['name'][$index], 379 | isset($_SERVER['HTTP_X_FILE_SIZE']) ? 380 | $_SERVER['HTTP_X_FILE_SIZE'] : $upload['size'][$index], 381 | isset($_SERVER['HTTP_X_FILE_TYPE']) ? 382 | $_SERVER['HTTP_X_FILE_TYPE'] : $upload['type'][$index], 383 | $upload['error'][$index], 384 | $index 385 | ); 386 | } 387 | } elseif ($upload || isset($_SERVER['HTTP_X_FILE_NAME'])) { 388 | // param_name is a single object identifier like "file", 389 | // $_FILES is a one-dimensional array: 390 | $info[] = $this->handle_file_upload($folder, 391 | isset($upload['tmp_name']) ? $upload['tmp_name'] : null, 392 | isset($_SERVER['HTTP_X_FILE_NAME']) ? 393 | $_SERVER['HTTP_X_FILE_NAME'] : (isset($upload['name']) ? 394 | $upload['name'] : null), 395 | isset($_SERVER['HTTP_X_FILE_SIZE']) ? 396 | $_SERVER['HTTP_X_FILE_SIZE'] : (isset($upload['size']) ? 397 | $upload['size'] : null), 398 | isset($_SERVER['HTTP_X_FILE_TYPE']) ? 399 | $_SERVER['HTTP_X_FILE_TYPE'] : (isset($upload['type']) ? 400 | $upload['type'] : null), 401 | isset($upload['error']) ? $upload['error'] : null 402 | ); 403 | } 404 | header('Vary: Accept'); 405 | $json = json_encode($info); 406 | $redirect = isset($_REQUEST['redirect']) ? 407 | stripslashes($_REQUEST['redirect']) : null; 408 | if ($redirect) { 409 | header('Location: '.sprintf($redirect, rawurlencode($json))); 410 | return; 411 | } 412 | if (isset($_SERVER['HTTP_ACCEPT']) && 413 | (strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false)) { 414 | header('Content-type: application/json'); 415 | } else { 416 | header('Content-type: text/plain'); 417 | } 418 | echo $json; 419 | } 420 | 421 | public function delete($folder) { 422 | $file_name = isset($_REQUEST['file']) ? 423 | basename(stripslashes($_REQUEST['file'])) : null; 424 | $file_path = $this->options['upload_dir'].$folder.$file_name; 425 | $success = is_file($file_path) && $file_name[0] !== '.' && unlink($file_path); 426 | if ($success) { 427 | foreach($this->options['image_versions'] as $version => $options) { 428 | $file = $options['upload_dir'].$folder.$file_name; 429 | if (is_file($file)) { 430 | unlink($file); 431 | } 432 | } 433 | } 434 | header('Content-type: application/json'); 435 | echo json_encode($success); 436 | } 437 | 438 | } -------------------------------------------------------------------------------- /public/css/jquery.fileupload-ui.css: -------------------------------------------------------------------------------- 1 | @charset 'UTF-8'; 2 | /* 3 | * jQuery File Upload UI Plugin CSS 6.3 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2010, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | .fileinput-button { 14 | position: relative; 15 | overflow: hidden; 16 | float: left; 17 | margin-right: 4px; 18 | } 19 | .fileinput-button input { 20 | position: absolute; 21 | top: 0; 22 | right: 0; 23 | margin: 0; 24 | border: solid transparent; 25 | border-width: 0 0 100px 200px; 26 | opacity: 0; 27 | filter: alpha(opacity=0); 28 | -moz-transform: translate(-300px, 0) scale(4); 29 | direction: ltr; 30 | cursor: pointer; 31 | } 32 | .fileupload-buttonbar .btn, 33 | .fileupload-buttonbar .toggle { 34 | margin-bottom: 5px; 35 | } 36 | .files .progress { 37 | width: 200px; 38 | } 39 | .progress-animated .bar { 40 | background: url(../img/progressbar.gif) !important; 41 | filter: none; 42 | } 43 | .fileupload-loading { 44 | position: absolute; 45 | left: 50%; 46 | width: 128px; 47 | height: 128px; 48 | background: url(../img/loading.gif) center no-repeat; 49 | display: none; 50 | } 51 | .fileupload-processing .fileupload-loading { 52 | display: block; 53 | } 54 | 55 | /* Fix for IE 6: */ 56 | *html .fileinput-button { 57 | line-height: 22px; 58 | margin: 1px -3px 0 0; 59 | } 60 | 61 | /* Fix for IE 7: */ 62 | *+html .fileinput-button { 63 | margin: 1px 0 0 0; 64 | } 65 | 66 | @media (max-width: 480px) { 67 | .files .btn span { 68 | display: none; 69 | } 70 | .files .preview * { 71 | width: 40px; 72 | } 73 | .files .name * { 74 | width: 80px; 75 | display: inline-block; 76 | word-wrap: break-word; 77 | } 78 | .files .progress { 79 | width: 20px; 80 | } 81 | .files .delete { 82 | width: 60px; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | @charset 'UTF-8'; 2 | /* 3 | * jQuery File Upload Plugin CSS Example 1.0 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2012, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | body{ 14 | padding-top: 60px; 15 | } 16 | -------------------------------------------------------------------------------- /public/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zimt28/laravel-jquery-file-upload/be8a238c37c9675664a73e5b75d6d512d23fde3b/public/img/loading.gif -------------------------------------------------------------------------------- /public/img/progressbar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zimt28/laravel-jquery-file-upload/be8a238c37c9675664a73e5b75d6d512d23fde3b/public/img/progressbar.gif -------------------------------------------------------------------------------- /public/js/cors/jquery.postmessage-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery postMessage Transport Plugin 1.1 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /*jslint unparam: true, nomen: true */ 13 | /*global define, window, document */ 14 | 15 | (function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define(['jquery'], factory); 20 | } else { 21 | // Browser globals: 22 | factory(window.jQuery); 23 | } 24 | }(function ($) { 25 | 'use strict'; 26 | 27 | var counter = 0, 28 | names = [ 29 | 'accepts', 30 | 'cache', 31 | 'contents', 32 | 'contentType', 33 | 'crossDomain', 34 | 'data', 35 | 'dataType', 36 | 'headers', 37 | 'ifModified', 38 | 'mimeType', 39 | 'password', 40 | 'processData', 41 | 'timeout', 42 | 'traditional', 43 | 'type', 44 | 'url', 45 | 'username' 46 | ], 47 | convert = function (p) { 48 | return p; 49 | }; 50 | 51 | $.ajaxSetup({ 52 | converters: { 53 | 'postmessage text': convert, 54 | 'postmessage json': convert, 55 | 'postmessage html': convert 56 | } 57 | }); 58 | 59 | $.ajaxTransport('postmessage', function (options) { 60 | if (options.postMessage && window.postMessage) { 61 | var iframe, 62 | loc = $('').prop('href', options.postMessage)[0], 63 | target = loc.protocol + '//' + loc.host, 64 | xhrUpload = options.xhr().upload; 65 | return { 66 | send: function (_, completeCallback) { 67 | var message = { 68 | id: 'postmessage-transport-' + (counter += 1) 69 | }, 70 | eventName = 'message.' + message.id; 71 | iframe = $( 72 | '' 75 | ).bind('load', function () { 76 | $.each(names, function (i, name) { 77 | message[name] = options[name]; 78 | }); 79 | message.dataType = message.dataType.replace('postmessage ', ''); 80 | $(window).bind(eventName, function (e) { 81 | e = e.originalEvent; 82 | var data = e.data, 83 | ev; 84 | if (e.origin === target && data.id === message.id) { 85 | if (data.type === 'progress') { 86 | ev = document.createEvent('Event'); 87 | ev.initEvent(data.type, false, true); 88 | $.extend(ev, data); 89 | xhrUpload.dispatchEvent(ev); 90 | } else { 91 | completeCallback( 92 | data.status, 93 | data.statusText, 94 | {postmessage: data.result}, 95 | data.headers 96 | ); 97 | iframe.remove(); 98 | $(window).unbind(eventName); 99 | } 100 | } 101 | }); 102 | iframe[0].contentWindow.postMessage( 103 | message, 104 | target 105 | ); 106 | }).appendTo(document.body); 107 | }, 108 | abort: function () { 109 | if (iframe) { 110 | iframe.remove(); 111 | } 112 | } 113 | }; 114 | } 115 | }); 116 | 117 | })); 118 | -------------------------------------------------------------------------------- /public/js/cors/jquery.xdr-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery XDomainRequest Transport Plugin 1.1.2 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | * 11 | * Based on Julian Aubourg's ajaxHooks xdr.js: 12 | * https://github.com/jaubourg/ajaxHooks/ 13 | */ 14 | 15 | /*jslint unparam: true */ 16 | /*global define, window, XDomainRequest */ 17 | 18 | (function (factory) { 19 | 'use strict'; 20 | if (typeof define === 'function' && define.amd) { 21 | // Register as an anonymous AMD module: 22 | define(['jquery'], factory); 23 | } else { 24 | // Browser globals: 25 | factory(window.jQuery); 26 | } 27 | }(function ($) { 28 | 'use strict'; 29 | if (window.XDomainRequest && !$.support.cors) { 30 | $.ajaxTransport(function (s) { 31 | if (s.crossDomain && s.async) { 32 | if (s.timeout) { 33 | s.xdrTimeout = s.timeout; 34 | delete s.timeout; 35 | } 36 | var xdr; 37 | return { 38 | send: function (headers, completeCallback) { 39 | function callback(status, statusText, responses, responseHeaders) { 40 | xdr.onload = xdr.onerror = xdr.ontimeout = $.noop; 41 | xdr = null; 42 | completeCallback(status, statusText, responses, responseHeaders); 43 | } 44 | xdr = new XDomainRequest(); 45 | // XDomainRequest only supports GET and POST: 46 | if (s.type === 'DELETE') { 47 | s.url = s.url + (/\?/.test(s.url) ? '&' : '?') + 48 | '_method=DELETE'; 49 | s.type = 'POST'; 50 | } else if (s.type === 'PUT') { 51 | s.url = s.url + (/\?/.test(s.url) ? '&' : '?') + 52 | '_method=PUT'; 53 | s.type = 'POST'; 54 | } 55 | xdr.open(s.type, s.url); 56 | xdr.onload = function () { 57 | callback( 58 | 200, 59 | 'OK', 60 | {text: xdr.responseText}, 61 | 'Content-Type: ' + xdr.contentType 62 | ); 63 | }; 64 | xdr.onerror = function () { 65 | callback(404, 'Not Found'); 66 | }; 67 | if (s.xdrTimeout) { 68 | xdr.ontimeout = function () { 69 | callback(0, 'timeout'); 70 | }; 71 | xdr.timeout = s.xdrTimeout; 72 | } 73 | xdr.send((s.hasContent && s.data) || null); 74 | }, 75 | abort: function () { 76 | if (xdr) { 77 | xdr.onerror = $.noop(); 78 | xdr.abort(); 79 | } 80 | } 81 | }; 82 | } 83 | }); 84 | } 85 | })); 86 | -------------------------------------------------------------------------------- /public/js/jquery.fileupload-fp.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload File Processing Plugin 1.0 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2012, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /*jslint nomen: true, unparam: true, regexp: true */ 13 | /*global define, window, document */ 14 | 15 | (function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | 'load-image', 22 | 'canvas-to-blob', 23 | './jquery.fileupload' 24 | ], factory); 25 | } else { 26 | // Browser globals: 27 | factory( 28 | window.jQuery, 29 | window.loadImage 30 | ); 31 | } 32 | }(function ($, loadImage) { 33 | 'use strict'; 34 | 35 | // The File Upload IP version extends the basic fileupload widget 36 | // with file processing functionality: 37 | $.widget('blueimpFP.fileupload', $.blueimp.fileupload, { 38 | 39 | options: { 40 | // The list of file processing actions: 41 | process: [ 42 | /* 43 | { 44 | action: 'load', 45 | fileTypes: /^image\/(gif|jpeg|png)$/, 46 | maxFileSize: 20000000 // 20MB 47 | }, 48 | { 49 | action: 'resize', 50 | maxWidth: 1920, 51 | maxHeight: 1200, 52 | minWidth: 800, 53 | minHeight: 600 54 | }, 55 | { 56 | action: 'save' 57 | } 58 | */ 59 | ], 60 | 61 | // The add callback is invoked as soon as files are added to the 62 | // fileupload widget (via file input selection, drag & drop or add 63 | // API call). See the basic file upload widget for more information: 64 | add: function (e, data) { 65 | $(this).fileupload('process', data).done(function () { 66 | data.submit(); 67 | }); 68 | } 69 | }, 70 | 71 | processActions: { 72 | // Loads the image given via data.files and data.index 73 | // as canvas element. 74 | // Accepts the options fileTypes (regular expression) 75 | // and maxFileSize (integer) to limit the files to load: 76 | load: function (data, options) { 77 | var that = this, 78 | file = data.files[data.index], 79 | dfd = $.Deferred(); 80 | if (window.HTMLCanvasElement && 81 | window.HTMLCanvasElement.prototype.toBlob && 82 | ($.type(options.maxFileSize) !== 'number' || 83 | file.size < options.maxFileSize) && 84 | (!options.fileTypes || 85 | options.fileTypes.test(file.type))) { 86 | loadImage( 87 | file, 88 | function (canvas) { 89 | data.canvas = canvas; 90 | dfd.resolveWith(that, [data]); 91 | }, 92 | {canvas: true} 93 | ); 94 | } else { 95 | dfd.rejectWith(that, [data]); 96 | } 97 | return dfd.promise(); 98 | }, 99 | // Resizes the image given as data.canvas and updates 100 | // data.canvas with the resized image. 101 | // Accepts the options maxWidth, maxHeight, minWidth and 102 | // minHeight to scale the given image: 103 | resize: function (data, options) { 104 | if (data.canvas) { 105 | var canvas = loadImage.scale(data.canvas, options); 106 | if (canvas.width !== data.canvas.width || 107 | canvas.height !== data.canvas.height) { 108 | data.canvas = canvas; 109 | data.processed = true; 110 | } 111 | } 112 | return data; 113 | }, 114 | // Saves the processed image given as data.canvas 115 | // inplace at data.index of data.files: 116 | save: function (data, options) { 117 | // Do nothing if no processing has happened: 118 | if (!data.canvas || !data.processed) { 119 | return data; 120 | } 121 | var that = this, 122 | file = data.files[data.index], 123 | name = file.name, 124 | dfd = $.Deferred(), 125 | callback = function (blob) { 126 | if (!blob.name) { 127 | if (file.type === blob.type) { 128 | blob.name = file.name; 129 | } else if (file.name) { 130 | blob.name = file.name.replace( 131 | /\..+$/, 132 | '.' + blob.type.substr(6) 133 | ); 134 | } 135 | } 136 | // Store the created blob at the position 137 | // of the original file in the files list: 138 | data.files[data.index] = blob; 139 | dfd.resolveWith(that, [data]); 140 | }; 141 | // Use canvas.mozGetAsFile directly, to retain the filename, as 142 | // Gecko doesn't support the filename option for FormData.append: 143 | if (data.canvas.mozGetAsFile) { 144 | callback(data.canvas.mozGetAsFile( 145 | (/^image\/(jpeg|png)$/.test(file.type) && name) || 146 | ((name && name.replace(/\..+$/, '')) || 147 | 'blob') + '.png', 148 | file.type 149 | )); 150 | } else { 151 | data.canvas.toBlob(callback, file.type); 152 | } 153 | return dfd.promise(); 154 | } 155 | }, 156 | 157 | // Resizes the file at the given index and stores the created blob at 158 | // the original position of the files list, returns a Promise object: 159 | _processFile: function (files, index, options) { 160 | var that = this, 161 | dfd = $.Deferred().resolveWith(that, [{ 162 | files: files, 163 | index: index 164 | }]), 165 | chain = dfd.promise(); 166 | that._processing += 1; 167 | $.each(options.process, function (i, settings) { 168 | chain = chain.pipe(function (data) { 169 | return that.processActions[settings.action] 170 | .call(this, data, settings); 171 | }); 172 | }); 173 | chain.always(function () { 174 | that._processing -= 1; 175 | if (that._processing === 0) { 176 | that.element 177 | .removeClass('fileupload-processing'); 178 | } 179 | }); 180 | if (that._processing === 1) { 181 | that.element.addClass('fileupload-processing'); 182 | } 183 | return chain; 184 | }, 185 | 186 | // Processes the files given as files property of the data parameter, 187 | // returns a Promise object that allows to bind a done handler, which 188 | // will be invoked after processing all files (inplace) is done: 189 | process: function (data) { 190 | var that = this, 191 | options = $.extend({}, this.options, data); 192 | if (options.process && options.process.length && 193 | this._isXHRUpload(options)) { 194 | $.each(data.files, function (index, file) { 195 | that._processingQueue = that._processingQueue.pipe( 196 | function () { 197 | var dfd = $.Deferred(); 198 | that._processFile(data.files, index, options) 199 | .always(function () { 200 | dfd.resolveWith(that); 201 | }); 202 | return dfd.promise(); 203 | } 204 | ); 205 | }); 206 | } 207 | return this._processingQueue; 208 | }, 209 | 210 | _create: function () { 211 | $.blueimp.fileupload.prototype._create.call(this); 212 | this._processing = 0; 213 | this._processingQueue = $.Deferred().resolveWith(this) 214 | .promise(); 215 | } 216 | 217 | }); 218 | 219 | })); 220 | -------------------------------------------------------------------------------- /public/js/jquery.fileupload-ui.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload User Interface Plugin 6.9.5 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2010, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /*jslint nomen: true, unparam: true, regexp: true */ 13 | /*global define, window, document, URL, webkitURL, FileReader */ 14 | 15 | (function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | 'tmpl', 22 | 'load-image', 23 | './jquery.fileupload-fp' 24 | ], factory); 25 | } else { 26 | // Browser globals: 27 | factory( 28 | window.jQuery, 29 | window.tmpl, 30 | window.loadImage 31 | ); 32 | } 33 | }(function ($, tmpl, loadImage) { 34 | 'use strict'; 35 | 36 | // The UI version extends the FP (file processing) version or the basic 37 | // file upload widget and adds complete user interface interaction: 38 | var parentWidget = ($.blueimpFP || $.blueimp).fileupload; 39 | $.widget('blueimpUI.fileupload', parentWidget, { 40 | 41 | options: { 42 | // By default, files added to the widget are uploaded as soon 43 | // as the user clicks on the start buttons. To enable automatic 44 | // uploads, set the following option to true: 45 | autoUpload: false, 46 | // The following option limits the number of files that are 47 | // allowed to be uploaded using this widget: 48 | maxNumberOfFiles: undefined, 49 | // The maximum allowed file size: 50 | maxFileSize: undefined, 51 | // The minimum allowed file size: 52 | minFileSize: undefined, 53 | // The regular expression for allowed file types, matches 54 | // against either file type or file name: 55 | acceptFileTypes: /.+$/i, 56 | // The regular expression to define for which files a preview 57 | // image is shown, matched against the file type: 58 | previewSourceFileTypes: /^image\/(gif|jpeg|png)$/, 59 | // The maximum file size of images that are to be displayed as preview: 60 | previewSourceMaxFileSize: 5000000, // 5MB 61 | // The maximum width of the preview images: 62 | previewMaxWidth: 80, 63 | // The maximum height of the preview images: 64 | previewMaxHeight: 80, 65 | // By default, preview images are displayed as canvas elements 66 | // if supported by the browser. Set the following option to false 67 | // to always display preview images as img elements: 68 | previewAsCanvas: true, 69 | // The ID of the upload template: 70 | uploadTemplateId: 'template-upload', 71 | // The ID of the download template: 72 | downloadTemplateId: 'template-download', 73 | // The container for the list of files. If undefined, it is set to 74 | // an element with class "files" inside of the widget element: 75 | filesContainer: undefined, 76 | // By default, files are appended to the files container. 77 | // Set the following option to true, to prepend files instead: 78 | prependFiles: false, 79 | // The expected data type of the upload response, sets the dataType 80 | // option of the $.ajax upload requests: 81 | dataType: 'json', 82 | 83 | // The add callback is invoked as soon as files are added to the fileupload 84 | // widget (via file input selection, drag & drop or add API call). 85 | // See the basic file upload widget for more information: 86 | add: function (e, data) { 87 | var that = $(this).data('fileupload'), 88 | options = that.options, 89 | files = data.files; 90 | $(this).fileupload('process', data).done(function () { 91 | that._adjustMaxNumberOfFiles(-files.length); 92 | data.maxNumberOfFilesAdjusted = true; 93 | data.files.valid = data.isValidated = that._validate(files); 94 | data.context = that._renderUpload(files).data('data', data); 95 | options.filesContainer[ 96 | options.prependFiles ? 'prepend' : 'append' 97 | ](data.context); 98 | that._renderPreviews(files, data.context); 99 | that._forceReflow(data.context); 100 | that._transition(data.context).done( 101 | function () { 102 | if ((that._trigger('added', e, data) !== false) && 103 | (options.autoUpload || data.autoUpload) && 104 | data.autoUpload !== false && data.isValidated) { 105 | data.submit(); 106 | } 107 | } 108 | ); 109 | }); 110 | }, 111 | // Callback for the start of each file upload request: 112 | send: function (e, data) { 113 | var that = $(this).data('fileupload'); 114 | if (!data.isValidated) { 115 | if (!data.maxNumberOfFilesAdjusted) { 116 | that._adjustMaxNumberOfFiles(-data.files.length); 117 | data.maxNumberOfFilesAdjusted = true; 118 | } 119 | if (!that._validate(data.files)) { 120 | return false; 121 | } 122 | } 123 | if (data.context && data.dataType && 124 | data.dataType.substr(0, 6) === 'iframe') { 125 | // Iframe Transport does not support progress events. 126 | // In lack of an indeterminate progress bar, we set 127 | // the progress to 100%, showing the full animated bar: 128 | data.context 129 | .find('.progress').addClass( 130 | !$.support.transition && 'progress-animated' 131 | ) 132 | .attr('aria-valuenow', 100) 133 | .find('.bar').css( 134 | 'width', 135 | '100%' 136 | ); 137 | } 138 | return that._trigger('sent', e, data); 139 | }, 140 | // Callback for successful uploads: 141 | done: function (e, data) { 142 | var that = $(this).data('fileupload'), 143 | template; 144 | if (data.context) { 145 | data.context.each(function (index) { 146 | var file = ($.isArray(data.result) && 147 | data.result[index]) || {error: 'emptyResult'}; 148 | if (file.error) { 149 | that._adjustMaxNumberOfFiles(1); 150 | } 151 | that._transition($(this)).done( 152 | function () { 153 | var node = $(this); 154 | template = that._renderDownload([file]) 155 | .replaceAll(node); 156 | that._forceReflow(template); 157 | that._transition(template).done( 158 | function () { 159 | data.context = $(this); 160 | that._trigger('completed', e, data); 161 | } 162 | ); 163 | } 164 | ); 165 | }); 166 | } else { 167 | if ($.isArray(data.result)) { 168 | $.each(data.result, function (index, file) { 169 | if (data.maxNumberOfFilesAdjusted && file.error) { 170 | that._adjustMaxNumberOfFiles(1); 171 | } else if (!data.maxNumberOfFilesAdjusted && 172 | !file.error) { 173 | that._adjustMaxNumberOfFiles(-1); 174 | } 175 | }); 176 | data.maxNumberOfFilesAdjusted = true; 177 | } 178 | template = that._renderDownload(data.result) 179 | .appendTo(that.options.filesContainer); 180 | that._forceReflow(template); 181 | that._transition(template).done( 182 | function () { 183 | data.context = $(this); 184 | that._trigger('completed', e, data); 185 | } 186 | ); 187 | } 188 | }, 189 | // Callback for failed (abort or error) uploads: 190 | fail: function (e, data) { 191 | var that = $(this).data('fileupload'), 192 | template; 193 | if (data.maxNumberOfFilesAdjusted) { 194 | that._adjustMaxNumberOfFiles(data.files.length); 195 | } 196 | if (data.context) { 197 | data.context.each(function (index) { 198 | if (data.errorThrown !== 'abort') { 199 | var file = data.files[index]; 200 | file.error = file.error || data.errorThrown || 201 | true; 202 | that._transition($(this)).done( 203 | function () { 204 | var node = $(this); 205 | template = that._renderDownload([file]) 206 | .replaceAll(node); 207 | that._forceReflow(template); 208 | that._transition(template).done( 209 | function () { 210 | data.context = $(this); 211 | that._trigger('failed', e, data); 212 | } 213 | ); 214 | } 215 | ); 216 | } else { 217 | that._transition($(this)).done( 218 | function () { 219 | $(this).remove(); 220 | that._trigger('failed', e, data); 221 | } 222 | ); 223 | } 224 | }); 225 | } else if (data.errorThrown !== 'abort') { 226 | data.context = that._renderUpload(data.files) 227 | .appendTo(that.options.filesContainer) 228 | .data('data', data); 229 | that._forceReflow(data.context); 230 | that._transition(data.context).done( 231 | function () { 232 | data.context = $(this); 233 | that._trigger('failed', e, data); 234 | } 235 | ); 236 | } else { 237 | that._trigger('failed', e, data); 238 | } 239 | }, 240 | // Callback for upload progress events: 241 | progress: function (e, data) { 242 | if (data.context) { 243 | var progress = parseInt(data.loaded / data.total * 100, 10); 244 | data.context.find('.progress') 245 | .attr('aria-valuenow', progress) 246 | .find('.bar').css( 247 | 'width', 248 | progress + '%' 249 | ); 250 | } 251 | }, 252 | // Callback for global upload progress events: 253 | progressall: function (e, data) { 254 | var $this = $(this), 255 | progress = parseInt(data.loaded / data.total * 100, 10), 256 | globalProgressNode = $this.find('.fileupload-progress'), 257 | extendedProgressNode = globalProgressNode 258 | .find('.progress-extended'); 259 | if (extendedProgressNode.length) { 260 | extendedProgressNode.html( 261 | $this.data('fileupload')._renderExtendedProgress(data) 262 | ); 263 | } 264 | globalProgressNode 265 | .find('.progress') 266 | .attr('aria-valuenow', progress) 267 | .find('.bar').css( 268 | 'width', 269 | progress + '%' 270 | ); 271 | }, 272 | // Callback for uploads start, equivalent to the global ajaxStart event: 273 | start: function (e) { 274 | var that = $(this).data('fileupload'); 275 | that._transition($(this).find('.fileupload-progress')).done( 276 | function () { 277 | that._trigger('started', e); 278 | } 279 | ); 280 | }, 281 | // Callback for uploads stop, equivalent to the global ajaxStop event: 282 | stop: function (e) { 283 | var that = $(this).data('fileupload'); 284 | that._transition($(this).find('.fileupload-progress')).done( 285 | function () { 286 | $(this).find('.progress') 287 | .attr('aria-valuenow', '0') 288 | .find('.bar').css('width', '0%'); 289 | $(this).find('.progress-extended').html(' '); 290 | that._trigger('stopped', e); 291 | } 292 | ); 293 | }, 294 | // Callback for file deletion: 295 | destroy: function (e, data) { 296 | var that = $(this).data('fileupload'); 297 | if (data.url) { 298 | $.ajax(data); 299 | that._adjustMaxNumberOfFiles(1); 300 | } 301 | that._transition(data.context).done( 302 | function () { 303 | $(this).remove(); 304 | that._trigger('destroyed', e, data); 305 | } 306 | ); 307 | } 308 | }, 309 | 310 | // Link handler, that allows to download files 311 | // by drag & drop of the links to the desktop: 312 | _enableDragToDesktop: function () { 313 | var link = $(this), 314 | url = link.prop('href'), 315 | name = link.prop('download'), 316 | type = 'application/octet-stream'; 317 | link.bind('dragstart', function (e) { 318 | try { 319 | e.originalEvent.dataTransfer.setData( 320 | 'DownloadURL', 321 | [type, name, url].join(':') 322 | ); 323 | } catch (err) {} 324 | }); 325 | }, 326 | 327 | _adjustMaxNumberOfFiles: function (operand) { 328 | if (typeof this.options.maxNumberOfFiles === 'number') { 329 | this.options.maxNumberOfFiles += operand; 330 | if (this.options.maxNumberOfFiles < 1) { 331 | this._disableFileInputButton(); 332 | } else { 333 | this._enableFileInputButton(); 334 | } 335 | } 336 | }, 337 | 338 | _formatFileSize: function (bytes) { 339 | if (typeof bytes !== 'number') { 340 | return ''; 341 | } 342 | if (bytes >= 1000000000) { 343 | return (bytes / 1000000000).toFixed(2) + ' GB'; 344 | } 345 | if (bytes >= 1000000) { 346 | return (bytes / 1000000).toFixed(2) + ' MB'; 347 | } 348 | return (bytes / 1000).toFixed(2) + ' KB'; 349 | }, 350 | 351 | _formatBitrate: function (bits) { 352 | if (typeof bits !== 'number') { 353 | return ''; 354 | } 355 | if (bits >= 1000000000) { 356 | return (bits / 1000000000).toFixed(2) + ' Gbit/s'; 357 | } 358 | if (bits >= 1000000) { 359 | return (bits / 1000000).toFixed(2) + ' Mbit/s'; 360 | } 361 | if (bits >= 1000) { 362 | return (bits / 1000).toFixed(2) + ' kbit/s'; 363 | } 364 | return bits + ' bit/s'; 365 | }, 366 | 367 | _formatTime: function (seconds) { 368 | var date = new Date(seconds * 1000), 369 | days = parseInt(seconds / 86400, 10); 370 | days = days ? days + 'd ' : ''; 371 | return days + 372 | ('0' + date.getUTCHours()).slice(-2) + ':' + 373 | ('0' + date.getUTCMinutes()).slice(-2) + ':' + 374 | ('0' + date.getUTCSeconds()).slice(-2); 375 | }, 376 | 377 | _formatPercentage: function (floatValue) { 378 | return (floatValue * 100).toFixed(2) + ' %'; 379 | }, 380 | 381 | _renderExtendedProgress: function (data) { 382 | return this._formatBitrate(data.bitrate) + ' | ' + 383 | this._formatTime( 384 | (data.total - data.loaded) * 8 / data.bitrate 385 | ) + ' | ' + 386 | this._formatPercentage( 387 | data.loaded / data.total 388 | ) + ' | ' + 389 | this._formatFileSize(data.loaded) + ' / ' + 390 | this._formatFileSize(data.total); 391 | }, 392 | 393 | _hasError: function (file) { 394 | if (file.error) { 395 | return file.error; 396 | } 397 | // The number of added files is subtracted from 398 | // maxNumberOfFiles before validation, so we check if 399 | // maxNumberOfFiles is below 0 (instead of below 1): 400 | if (this.options.maxNumberOfFiles < 0) { 401 | return 'maxNumberOfFiles'; 402 | } 403 | // Files are accepted if either the file type or the file name 404 | // matches against the acceptFileTypes regular expression, as 405 | // only browsers with support for the File API report the type: 406 | if (!(this.options.acceptFileTypes.test(file.type) || 407 | this.options.acceptFileTypes.test(file.name))) { 408 | return 'acceptFileTypes'; 409 | } 410 | if (this.options.maxFileSize && 411 | file.size > this.options.maxFileSize) { 412 | return 'maxFileSize'; 413 | } 414 | if (typeof file.size === 'number' && 415 | file.size < this.options.minFileSize) { 416 | return 'minFileSize'; 417 | } 418 | return null; 419 | }, 420 | 421 | _validate: function (files) { 422 | var that = this, 423 | valid = !!files.length; 424 | $.each(files, function (index, file) { 425 | file.error = that._hasError(file); 426 | if (file.error) { 427 | valid = false; 428 | } 429 | }); 430 | return valid; 431 | }, 432 | 433 | _renderTemplate: function (func, files) { 434 | if (!func) { 435 | return $(); 436 | } 437 | var result = func({ 438 | files: files, 439 | formatFileSize: this._formatFileSize, 440 | options: this.options 441 | }); 442 | if (result instanceof $) { 443 | return result; 444 | } 445 | return $(this.options.templatesContainer).html(result).children(); 446 | }, 447 | 448 | _renderPreview: function (file, node) { 449 | var that = this, 450 | options = this.options, 451 | dfd = $.Deferred(); 452 | return ((loadImage && loadImage( 453 | file, 454 | function (img) { 455 | node.append(img); 456 | that._forceReflow(node); 457 | that._transition(node).done(function () { 458 | dfd.resolveWith(node); 459 | }); 460 | if (!$.contains(document.body, node[0])) { 461 | // If the element is not part of the DOM, 462 | // transition events are not triggered, 463 | // so we have to resolve manually: 464 | dfd.resolveWith(node); 465 | } 466 | }, 467 | { 468 | maxWidth: options.previewMaxWidth, 469 | maxHeight: options.previewMaxHeight, 470 | canvas: options.previewAsCanvas 471 | } 472 | )) || dfd.resolveWith(node)) && dfd; 473 | }, 474 | 475 | _renderPreviews: function (files, nodes) { 476 | var that = this, 477 | options = this.options; 478 | nodes.find('.preview span').each(function (index, element) { 479 | var file = files[index]; 480 | if (options.previewSourceFileTypes.test(file.type) && 481 | ($.type(options.previewSourceMaxFileSize) !== 'number' || 482 | file.size < options.previewSourceMaxFileSize)) { 483 | that._processingQueue = that._processingQueue.pipe(function () { 484 | var dfd = $.Deferred(); 485 | that._renderPreview(file, $(element)).done( 486 | function () { 487 | dfd.resolveWith(that); 488 | } 489 | ); 490 | return dfd.promise(); 491 | }); 492 | } 493 | }); 494 | return this._processingQueue; 495 | }, 496 | 497 | _renderUpload: function (files) { 498 | return this._renderTemplate( 499 | this.options.uploadTemplate, 500 | files 501 | ); 502 | }, 503 | 504 | _renderDownload: function (files) { 505 | return this._renderTemplate( 506 | this.options.downloadTemplate, 507 | files 508 | ).find('a[download]').each(this._enableDragToDesktop).end(); 509 | }, 510 | 511 | _startHandler: function (e) { 512 | e.preventDefault(); 513 | var button = $(this), 514 | template = button.closest('.template-upload'), 515 | data = template.data('data'); 516 | if (data && data.submit && !data.jqXHR && data.submit()) { 517 | button.prop('disabled', true); 518 | } 519 | }, 520 | 521 | _cancelHandler: function (e) { 522 | e.preventDefault(); 523 | var template = $(this).closest('.template-upload'), 524 | data = template.data('data') || {}; 525 | if (!data.jqXHR) { 526 | data.errorThrown = 'abort'; 527 | e.data.fileupload._trigger('fail', e, data); 528 | } else { 529 | data.jqXHR.abort(); 530 | } 531 | }, 532 | 533 | _deleteHandler: function (e) { 534 | e.preventDefault(); 535 | var button = $(this); 536 | e.data.fileupload._trigger('destroy', e, { 537 | context: button.closest('.template-download'), 538 | url: button.attr('data-url'), 539 | type: button.attr('data-type') || 'DELETE', 540 | dataType: e.data.fileupload.options.dataType 541 | }); 542 | }, 543 | 544 | _forceReflow: function (node) { 545 | return $.support.transition && node.length && 546 | node[0].offsetWidth; 547 | }, 548 | 549 | _transition: function (node) { 550 | var dfd = $.Deferred(); 551 | if ($.support.transition && node.hasClass('fade')) { 552 | node.bind( 553 | $.support.transition.end, 554 | function (e) { 555 | // Make sure we don't respond to other transitions events 556 | // in the container element, e.g. from button elements: 557 | if (e.target === node[0]) { 558 | node.unbind($.support.transition.end); 559 | dfd.resolveWith(node); 560 | } 561 | } 562 | ).toggleClass('in'); 563 | } else { 564 | node.toggleClass('in'); 565 | dfd.resolveWith(node); 566 | } 567 | return dfd; 568 | }, 569 | 570 | _initButtonBarEventHandlers: function () { 571 | var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'), 572 | filesList = this.options.filesContainer, 573 | ns = this.options.namespace; 574 | fileUploadButtonBar.find('.start') 575 | .bind('click.' + ns, function (e) { 576 | e.preventDefault(); 577 | filesList.find('.start button').click(); 578 | }); 579 | fileUploadButtonBar.find('.cancel') 580 | .bind('click.' + ns, function (e) { 581 | e.preventDefault(); 582 | filesList.find('.cancel button').click(); 583 | }); 584 | fileUploadButtonBar.find('.delete') 585 | .bind('click.' + ns, function (e) { 586 | e.preventDefault(); 587 | filesList.find('.delete input:checked') 588 | .siblings('button').click(); 589 | fileUploadButtonBar.find('.toggle') 590 | .prop('checked', false); 591 | }); 592 | fileUploadButtonBar.find('.toggle') 593 | .bind('change.' + ns, function (e) { 594 | filesList.find('.delete input').prop( 595 | 'checked', 596 | $(this).is(':checked') 597 | ); 598 | }); 599 | }, 600 | 601 | _destroyButtonBarEventHandlers: function () { 602 | this.element.find('.fileupload-buttonbar button') 603 | .unbind('click.' + this.options.namespace); 604 | this.element.find('.fileupload-buttonbar .toggle') 605 | .unbind('change.' + this.options.namespace); 606 | }, 607 | 608 | _initEventHandlers: function () { 609 | parentWidget.prototype._initEventHandlers.call(this); 610 | var eventData = {fileupload: this}; 611 | this.options.filesContainer 612 | .delegate( 613 | '.start button', 614 | 'click.' + this.options.namespace, 615 | eventData, 616 | this._startHandler 617 | ) 618 | .delegate( 619 | '.cancel button', 620 | 'click.' + this.options.namespace, 621 | eventData, 622 | this._cancelHandler 623 | ) 624 | .delegate( 625 | '.delete button', 626 | 'click.' + this.options.namespace, 627 | eventData, 628 | this._deleteHandler 629 | ); 630 | this._initButtonBarEventHandlers(); 631 | }, 632 | 633 | _destroyEventHandlers: function () { 634 | var options = this.options; 635 | this._destroyButtonBarEventHandlers(); 636 | options.filesContainer 637 | .undelegate('.start button', 'click.' + options.namespace) 638 | .undelegate('.cancel button', 'click.' + options.namespace) 639 | .undelegate('.delete button', 'click.' + options.namespace); 640 | parentWidget.prototype._destroyEventHandlers.call(this); 641 | }, 642 | 643 | _enableFileInputButton: function () { 644 | this.element.find('.fileinput-button input') 645 | .prop('disabled', false) 646 | .parent().removeClass('disabled'); 647 | }, 648 | 649 | _disableFileInputButton: function () { 650 | this.element.find('.fileinput-button input') 651 | .prop('disabled', true) 652 | .parent().addClass('disabled'); 653 | }, 654 | 655 | _initTemplates: function () { 656 | var options = this.options; 657 | options.templatesContainer = document.createElement( 658 | options.filesContainer.prop('nodeName') 659 | ); 660 | if (tmpl) { 661 | if (options.uploadTemplateId) { 662 | options.uploadTemplate = tmpl(options.uploadTemplateId); 663 | } 664 | if (options.downloadTemplateId) { 665 | options.downloadTemplate = tmpl(options.downloadTemplateId); 666 | } 667 | } 668 | }, 669 | 670 | _initFilesContainer: function () { 671 | var options = this.options; 672 | if (options.filesContainer === undefined) { 673 | options.filesContainer = this.element.find('.files'); 674 | } else if (!(options.filesContainer instanceof $)) { 675 | options.filesContainer = $(options.filesContainer); 676 | } 677 | }, 678 | 679 | _stringToRegExp: function (str) { 680 | var parts = str.split('/'), 681 | modifiers = parts.pop(); 682 | parts.shift(); 683 | return new RegExp(parts.join('/'), modifiers); 684 | }, 685 | 686 | _initRegExpOptions: function () { 687 | var options = this.options; 688 | if ($.type(options.acceptFileTypes) === 'string') { 689 | options.acceptFileTypes = this._stringToRegExp( 690 | options.acceptFileTypes 691 | ); 692 | } 693 | if ($.type(options.previewSourceFileTypes) === 'string') { 694 | options.previewSourceFileTypes = this._stringToRegExp( 695 | options.previewSourceFileTypes 696 | ); 697 | } 698 | }, 699 | 700 | _initSpecialOptions: function () { 701 | parentWidget.prototype._initSpecialOptions.call(this); 702 | this._initFilesContainer(); 703 | this._initTemplates(); 704 | this._initRegExpOptions(); 705 | }, 706 | 707 | _create: function () { 708 | parentWidget.prototype._create.call(this); 709 | this._refreshOptionsList.push( 710 | 'filesContainer', 711 | 'uploadTemplateId', 712 | 'downloadTemplateId' 713 | ); 714 | if (!$.blueimpFP) { 715 | this._processingQueue = $.Deferred().resolveWith(this).promise(); 716 | this.process = function () { 717 | return this._processingQueue; 718 | }; 719 | } 720 | }, 721 | 722 | enable: function () { 723 | var wasDisabled = false; 724 | if (this.options.disabled) { 725 | wasDisabled = true; 726 | } 727 | parentWidget.prototype.enable.call(this); 728 | if (wasDisabled) { 729 | this.element.find('input, button').prop('disabled', false); 730 | this._enableFileInputButton(); 731 | } 732 | }, 733 | 734 | disable: function () { 735 | if (!this.options.disabled) { 736 | this.element.find('input, button').prop('disabled', true); 737 | this._disableFileInputButton(); 738 | } 739 | parentWidget.prototype.disable.call(this); 740 | } 741 | 742 | }); 743 | 744 | })); 745 | -------------------------------------------------------------------------------- /public/js/jquery.fileupload.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Plugin 5.17.5 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2010, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /*jslint nomen: true, unparam: true, regexp: true */ 13 | /*global define, window, document, Blob, FormData, location */ 14 | 15 | (function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | 'jquery.ui.widget' 22 | ], factory); 23 | } else { 24 | // Browser globals: 25 | factory(window.jQuery); 26 | } 27 | }(function ($) { 28 | 'use strict'; 29 | 30 | // The FileReader API is not actually used, but works as feature detection, 31 | // as e.g. Safari supports XHR file uploads via the FormData API, 32 | // but not non-multipart XHR file uploads: 33 | $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader); 34 | $.support.xhrFormDataFileUpload = !!window.FormData; 35 | 36 | // The fileupload widget listens for change events on file input fields defined 37 | // via fileInput setting and paste or drop events of the given dropZone. 38 | // In addition to the default jQuery Widget methods, the fileupload widget 39 | // exposes the "add" and "send" methods, to add or directly send files using 40 | // the fileupload API. 41 | // By default, files added via file input selection, paste, drag & drop or 42 | // "add" method are uploaded immediately, but it is possible to override 43 | // the "add" callback option to queue file uploads. 44 | $.widget('blueimp.fileupload', { 45 | 46 | options: { 47 | // The namespace used for event handler binding on the fileInput, 48 | // dropZone and pasteZone document nodes. 49 | // If not set, the name of the widget ("fileupload") is used. 50 | namespace: undefined, 51 | // The drop target element(s), by the default the complete document. 52 | // Set to null to disable drag & drop support: 53 | dropZone: $(document), 54 | // The paste target element(s), by the default the complete document. 55 | // Set to null to disable paste support: 56 | pasteZone: $(document), 57 | // The file input field(s), that are listened to for change events. 58 | // If undefined, it is set to the file input fields inside 59 | // of the widget element on plugin initialization. 60 | // Set to null to disable the change listener. 61 | fileInput: undefined, 62 | // By default, the file input field is replaced with a clone after 63 | // each input field change event. This is required for iframe transport 64 | // queues and allows change events to be fired for the same file 65 | // selection, but can be disabled by setting the following option to false: 66 | replaceFileInput: true, 67 | // The parameter name for the file form data (the request argument name). 68 | // If undefined or empty, the name property of the file input field is 69 | // used, or "files[]" if the file input name property is also empty, 70 | // can be a string or an array of strings: 71 | paramName: undefined, 72 | // By default, each file of a selection is uploaded using an individual 73 | // request for XHR type uploads. Set to false to upload file 74 | // selections in one request each: 75 | singleFileUploads: true, 76 | // To limit the number of files uploaded with one XHR request, 77 | // set the following option to an integer greater than 0: 78 | limitMultiFileUploads: undefined, 79 | // Set the following option to true to issue all file upload requests 80 | // in a sequential order: 81 | sequentialUploads: false, 82 | // To limit the number of concurrent uploads, 83 | // set the following option to an integer greater than 0: 84 | limitConcurrentUploads: undefined, 85 | // Set the following option to true to force iframe transport uploads: 86 | forceIframeTransport: false, 87 | // Set the following option to the location of a redirect url on the 88 | // origin server, for cross-domain iframe transport uploads: 89 | redirect: undefined, 90 | // The parameter name for the redirect url, sent as part of the form 91 | // data and set to 'redirect' if this option is empty: 92 | redirectParamName: undefined, 93 | // Set the following option to the location of a postMessage window, 94 | // to enable postMessage transport uploads: 95 | postMessage: undefined, 96 | // By default, XHR file uploads are sent as multipart/form-data. 97 | // The iframe transport is always using multipart/form-data. 98 | // Set to false to enable non-multipart XHR uploads: 99 | multipart: true, 100 | // To upload large files in smaller chunks, set the following option 101 | // to a preferred maximum chunk size. If set to 0, null or undefined, 102 | // or the browser does not support the required Blob API, files will 103 | // be uploaded as a whole. 104 | maxChunkSize: undefined, 105 | // When a non-multipart upload or a chunked multipart upload has been 106 | // aborted, this option can be used to resume the upload by setting 107 | // it to the size of the already uploaded bytes. This option is most 108 | // useful when modifying the options object inside of the "add" or 109 | // "send" callbacks, as the options are cloned for each file upload. 110 | uploadedBytes: undefined, 111 | // By default, failed (abort or error) file uploads are removed from the 112 | // global progress calculation. Set the following option to false to 113 | // prevent recalculating the global progress data: 114 | recalculateProgress: true, 115 | // Interval in milliseconds to calculate and trigger progress events: 116 | progressInterval: 100, 117 | // Interval in milliseconds to calculate progress bitrate: 118 | bitrateInterval: 500, 119 | 120 | // Additional form data to be sent along with the file uploads can be set 121 | // using this option, which accepts an array of objects with name and 122 | // value properties, a function returning such an array, a FormData 123 | // object (for XHR file uploads), or a simple object. 124 | // The form of the first fileInput is given as parameter to the function: 125 | formData: function (form) { 126 | return form.serializeArray(); 127 | }, 128 | 129 | // The add callback is invoked as soon as files are added to the fileupload 130 | // widget (via file input selection, drag & drop, paste or add API call). 131 | // If the singleFileUploads option is enabled, this callback will be 132 | // called once for each file in the selection for XHR file uplaods, else 133 | // once for each file selection. 134 | // The upload starts when the submit method is invoked on the data parameter. 135 | // The data object contains a files property holding the added files 136 | // and allows to override plugin options as well as define ajax settings. 137 | // Listeners for this callback can also be bound the following way: 138 | // .bind('fileuploadadd', func); 139 | // data.submit() returns a Promise object and allows to attach additional 140 | // handlers using jQuery's Deferred callbacks: 141 | // data.submit().done(func).fail(func).always(func); 142 | add: function (e, data) { 143 | data.submit(); 144 | }, 145 | 146 | // Other callbacks: 147 | // Callback for the submit event of each file upload: 148 | // submit: function (e, data) {}, // .bind('fileuploadsubmit', func); 149 | // Callback for the start of each file upload request: 150 | // send: function (e, data) {}, // .bind('fileuploadsend', func); 151 | // Callback for successful uploads: 152 | // done: function (e, data) {}, // .bind('fileuploaddone', func); 153 | // Callback for failed (abort or error) uploads: 154 | // fail: function (e, data) {}, // .bind('fileuploadfail', func); 155 | // Callback for completed (success, abort or error) requests: 156 | // always: function (e, data) {}, // .bind('fileuploadalways', func); 157 | // Callback for upload progress events: 158 | // progress: function (e, data) {}, // .bind('fileuploadprogress', func); 159 | // Callback for global upload progress events: 160 | // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func); 161 | // Callback for uploads start, equivalent to the global ajaxStart event: 162 | // start: function (e) {}, // .bind('fileuploadstart', func); 163 | // Callback for uploads stop, equivalent to the global ajaxStop event: 164 | // stop: function (e) {}, // .bind('fileuploadstop', func); 165 | // Callback for change events of the fileInput(s): 166 | // change: function (e, data) {}, // .bind('fileuploadchange', func); 167 | // Callback for paste events to the pasteZone(s): 168 | // paste: function (e, data) {}, // .bind('fileuploadpaste', func); 169 | // Callback for drop events of the dropZone(s): 170 | // drop: function (e, data) {}, // .bind('fileuploaddrop', func); 171 | // Callback for dragover events of the dropZone(s): 172 | // dragover: function (e) {}, // .bind('fileuploaddragover', func); 173 | 174 | // The plugin options are used as settings object for the ajax calls. 175 | // The following are jQuery ajax settings required for the file uploads: 176 | processData: false, 177 | contentType: false, 178 | cache: false 179 | }, 180 | 181 | // A list of options that require a refresh after assigning a new value: 182 | _refreshOptionsList: [ 183 | 'namespace', 184 | 'fileInput', 185 | 'dropZone', 186 | 'pasteZone', 187 | 'multipart', 188 | 'forceIframeTransport' 189 | ], 190 | 191 | _BitrateTimer: function () { 192 | this.timestamp = +(new Date()); 193 | this.loaded = 0; 194 | this.bitrate = 0; 195 | this.getBitrate = function (now, loaded, interval) { 196 | var timeDiff = now - this.timestamp; 197 | if (!this.bitrate || !interval || timeDiff > interval) { 198 | this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8; 199 | this.loaded = loaded; 200 | this.timestamp = now; 201 | } 202 | return this.bitrate; 203 | }; 204 | }, 205 | 206 | _isXHRUpload: function (options) { 207 | return !options.forceIframeTransport && 208 | ((!options.multipart && $.support.xhrFileUpload) || 209 | $.support.xhrFormDataFileUpload); 210 | }, 211 | 212 | _getFormData: function (options) { 213 | var formData; 214 | if (typeof options.formData === 'function') { 215 | return options.formData(options.form); 216 | } 217 | if ($.isArray(options.formData)) { 218 | return options.formData; 219 | } 220 | if (options.formData) { 221 | formData = []; 222 | $.each(options.formData, function (name, value) { 223 | formData.push({name: name, value: value}); 224 | }); 225 | return formData; 226 | } 227 | return []; 228 | }, 229 | 230 | _getTotal: function (files) { 231 | var total = 0; 232 | $.each(files, function (index, file) { 233 | total += file.size || 1; 234 | }); 235 | return total; 236 | }, 237 | 238 | _onProgress: function (e, data) { 239 | if (e.lengthComputable) { 240 | var now = +(new Date()), 241 | total, 242 | loaded; 243 | if (data._time && data.progressInterval && 244 | (now - data._time < data.progressInterval) && 245 | e.loaded !== e.total) { 246 | return; 247 | } 248 | data._time = now; 249 | total = data.total || this._getTotal(data.files); 250 | loaded = parseInt( 251 | e.loaded / e.total * (data.chunkSize || total), 252 | 10 253 | ) + (data.uploadedBytes || 0); 254 | this._loaded += loaded - (data.loaded || data.uploadedBytes || 0); 255 | data.lengthComputable = true; 256 | data.loaded = loaded; 257 | data.total = total; 258 | data.bitrate = data._bitrateTimer.getBitrate( 259 | now, 260 | loaded, 261 | data.bitrateInterval 262 | ); 263 | // Trigger a custom progress event with a total data property set 264 | // to the file size(s) of the current upload and a loaded data 265 | // property calculated accordingly: 266 | this._trigger('progress', e, data); 267 | // Trigger a global progress event for all current file uploads, 268 | // including ajax calls queued for sequential file uploads: 269 | this._trigger('progressall', e, { 270 | lengthComputable: true, 271 | loaded: this._loaded, 272 | total: this._total, 273 | bitrate: this._bitrateTimer.getBitrate( 274 | now, 275 | this._loaded, 276 | data.bitrateInterval 277 | ) 278 | }); 279 | } 280 | }, 281 | 282 | _initProgressListener: function (options) { 283 | var that = this, 284 | xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr(); 285 | // Accesss to the native XHR object is required to add event listeners 286 | // for the upload progress event: 287 | if (xhr.upload) { 288 | $(xhr.upload).bind('progress', function (e) { 289 | var oe = e.originalEvent; 290 | // Make sure the progress event properties get copied over: 291 | e.lengthComputable = oe.lengthComputable; 292 | e.loaded = oe.loaded; 293 | e.total = oe.total; 294 | that._onProgress(e, options); 295 | }); 296 | options.xhr = function () { 297 | return xhr; 298 | }; 299 | } 300 | }, 301 | 302 | _initXHRData: function (options) { 303 | var formData, 304 | file = options.files[0], 305 | // Ignore non-multipart setting if not supported: 306 | multipart = options.multipart || !$.support.xhrFileUpload, 307 | paramName = options.paramName[0]; 308 | if (!multipart || options.blob) { 309 | // For non-multipart uploads and chunked uploads, 310 | // file meta data is not part of the request body, 311 | // so we transmit this data as part of the HTTP headers. 312 | // For cross domain requests, these headers must be allowed 313 | // via Access-Control-Allow-Headers or removed using 314 | // the beforeSend callback: 315 | options.headers = $.extend(options.headers, { 316 | 'X-File-Name': file.name, 317 | 'X-File-Type': file.type, 318 | 'X-File-Size': file.size 319 | }); 320 | if (!options.blob) { 321 | // Non-chunked non-multipart upload: 322 | options.contentType = file.type; 323 | options.data = file; 324 | } else if (!multipart) { 325 | // Chunked non-multipart upload: 326 | options.contentType = 'application/octet-stream'; 327 | options.data = options.blob; 328 | } 329 | } 330 | if (multipart && $.support.xhrFormDataFileUpload) { 331 | if (options.postMessage) { 332 | // window.postMessage does not allow sending FormData 333 | // objects, so we just add the File/Blob objects to 334 | // the formData array and let the postMessage window 335 | // create the FormData object out of this array: 336 | formData = this._getFormData(options); 337 | if (options.blob) { 338 | formData.push({ 339 | name: paramName, 340 | value: options.blob 341 | }); 342 | } else { 343 | $.each(options.files, function (index, file) { 344 | formData.push({ 345 | name: options.paramName[index] || paramName, 346 | value: file 347 | }); 348 | }); 349 | } 350 | } else { 351 | if (options.formData instanceof FormData) { 352 | formData = options.formData; 353 | } else { 354 | formData = new FormData(); 355 | $.each(this._getFormData(options), function (index, field) { 356 | formData.append(field.name, field.value); 357 | }); 358 | } 359 | if (options.blob) { 360 | formData.append(paramName, options.blob, file.name); 361 | } else { 362 | $.each(options.files, function (index, file) { 363 | // File objects are also Blob instances. 364 | // This check allows the tests to run with 365 | // dummy objects: 366 | if (file instanceof Blob) { 367 | formData.append( 368 | options.paramName[index] || paramName, 369 | file, 370 | file.name 371 | ); 372 | } 373 | }); 374 | } 375 | } 376 | options.data = formData; 377 | } 378 | // Blob reference is not needed anymore, free memory: 379 | options.blob = null; 380 | }, 381 | 382 | _initIframeSettings: function (options) { 383 | // Setting the dataType to iframe enables the iframe transport: 384 | options.dataType = 'iframe ' + (options.dataType || ''); 385 | // The iframe transport accepts a serialized array as form data: 386 | options.formData = this._getFormData(options); 387 | // Add redirect url to form data on cross-domain uploads: 388 | if (options.redirect && $('').prop('href', options.url) 389 | .prop('host') !== location.host) { 390 | options.formData.push({ 391 | name: options.redirectParamName || 'redirect', 392 | value: options.redirect 393 | }); 394 | } 395 | }, 396 | 397 | _initDataSettings: function (options) { 398 | if (this._isXHRUpload(options)) { 399 | if (!this._chunkedUpload(options, true)) { 400 | if (!options.data) { 401 | this._initXHRData(options); 402 | } 403 | this._initProgressListener(options); 404 | } 405 | if (options.postMessage) { 406 | // Setting the dataType to postmessage enables the 407 | // postMessage transport: 408 | options.dataType = 'postmessage ' + (options.dataType || ''); 409 | } 410 | } else { 411 | this._initIframeSettings(options, 'iframe'); 412 | } 413 | }, 414 | 415 | _getParamName: function (options) { 416 | var fileInput = $(options.fileInput), 417 | paramName = options.paramName; 418 | if (!paramName) { 419 | paramName = []; 420 | fileInput.each(function () { 421 | var input = $(this), 422 | name = input.prop('name') || 'files[]', 423 | i = (input.prop('files') || [1]).length; 424 | while (i) { 425 | paramName.push(name); 426 | i -= 1; 427 | } 428 | }); 429 | if (!paramName.length) { 430 | paramName = [fileInput.prop('name') || 'files[]']; 431 | } 432 | } else if (!$.isArray(paramName)) { 433 | paramName = [paramName]; 434 | } 435 | return paramName; 436 | }, 437 | 438 | _initFormSettings: function (options) { 439 | // Retrieve missing options from the input field and the 440 | // associated form, if available: 441 | if (!options.form || !options.form.length) { 442 | options.form = $(options.fileInput.prop('form')); 443 | // If the given file input doesn't have an associated form, 444 | // use the default widget file input's form: 445 | if (!options.form.length) { 446 | options.form = $(this.options.fileInput.prop('form')); 447 | } 448 | } 449 | options.paramName = this._getParamName(options); 450 | if (!options.url) { 451 | options.url = options.form.prop('action') || location.href; 452 | } 453 | // The HTTP request method must be "POST" or "PUT": 454 | options.type = (options.type || options.form.prop('method') || '') 455 | .toUpperCase(); 456 | if (options.type !== 'POST' && options.type !== 'PUT') { 457 | options.type = 'POST'; 458 | } 459 | if (!options.formAcceptCharset) { 460 | options.formAcceptCharset = options.form.attr('accept-charset'); 461 | } 462 | }, 463 | 464 | _getAJAXSettings: function (data) { 465 | var options = $.extend({}, this.options, data); 466 | this._initFormSettings(options); 467 | this._initDataSettings(options); 468 | return options; 469 | }, 470 | 471 | // Maps jqXHR callbacks to the equivalent 472 | // methods of the given Promise object: 473 | _enhancePromise: function (promise) { 474 | promise.success = promise.done; 475 | promise.error = promise.fail; 476 | promise.complete = promise.always; 477 | return promise; 478 | }, 479 | 480 | // Creates and returns a Promise object enhanced with 481 | // the jqXHR methods abort, success, error and complete: 482 | _getXHRPromise: function (resolveOrReject, context, args) { 483 | var dfd = $.Deferred(), 484 | promise = dfd.promise(); 485 | context = context || this.options.context || promise; 486 | if (resolveOrReject === true) { 487 | dfd.resolveWith(context, args); 488 | } else if (resolveOrReject === false) { 489 | dfd.rejectWith(context, args); 490 | } 491 | promise.abort = dfd.promise; 492 | return this._enhancePromise(promise); 493 | }, 494 | 495 | // Uploads a file in multiple, sequential requests 496 | // by splitting the file up in multiple blob chunks. 497 | // If the second parameter is true, only tests if the file 498 | // should be uploaded in chunks, but does not invoke any 499 | // upload requests: 500 | _chunkedUpload: function (options, testOnly) { 501 | var that = this, 502 | file = options.files[0], 503 | fs = file.size, 504 | ub = options.uploadedBytes = options.uploadedBytes || 0, 505 | mcs = options.maxChunkSize || fs, 506 | slice = file.slice || file.webkitSlice || file.mozSlice, 507 | upload, 508 | n, 509 | jqXHR, 510 | pipe; 511 | if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) || 512 | options.data) { 513 | return false; 514 | } 515 | if (testOnly) { 516 | return true; 517 | } 518 | if (ub >= fs) { 519 | file.error = 'uploadedBytes'; 520 | return this._getXHRPromise( 521 | false, 522 | options.context, 523 | [null, 'error', file.error] 524 | ); 525 | } 526 | // n is the number of blobs to upload, 527 | // calculated via filesize, uploaded bytes and max chunk size: 528 | n = Math.ceil((fs - ub) / mcs); 529 | // The chunk upload method accepting the chunk number as parameter: 530 | upload = function (i) { 531 | if (!i) { 532 | return that._getXHRPromise(true, options.context); 533 | } 534 | // Upload the blobs in sequential order: 535 | return upload(i -= 1).pipe(function () { 536 | // Clone the options object for each chunk upload: 537 | var o = $.extend({}, options); 538 | o.blob = slice.call( 539 | file, 540 | ub + i * mcs, 541 | ub + (i + 1) * mcs 542 | ); 543 | // Expose the chunk index: 544 | o.chunkIndex = i; 545 | // Expose the number of chunks: 546 | o.chunksNumber = n; 547 | // Store the current chunk size, as the blob itself 548 | // will be dereferenced after data processing: 549 | o.chunkSize = o.blob.size; 550 | // Process the upload data (the blob and potential form data): 551 | that._initXHRData(o); 552 | // Add progress listeners for this chunk upload: 553 | that._initProgressListener(o); 554 | jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context)) 555 | .done(function () { 556 | // Create a progress event if upload is done and 557 | // no progress event has been invoked for this chunk: 558 | if (!o.loaded) { 559 | that._onProgress($.Event('progress', { 560 | lengthComputable: true, 561 | loaded: o.chunkSize, 562 | total: o.chunkSize 563 | }), o); 564 | } 565 | options.uploadedBytes = o.uploadedBytes += 566 | o.chunkSize; 567 | }); 568 | return jqXHR; 569 | }); 570 | }; 571 | // Return the piped Promise object, enhanced with an abort method, 572 | // which is delegated to the jqXHR object of the current upload, 573 | // and jqXHR callbacks mapped to the equivalent Promise methods: 574 | pipe = upload(n); 575 | pipe.abort = function () { 576 | return jqXHR.abort(); 577 | }; 578 | return this._enhancePromise(pipe); 579 | }, 580 | 581 | _beforeSend: function (e, data) { 582 | if (this._active === 0) { 583 | // the start callback is triggered when an upload starts 584 | // and no other uploads are currently running, 585 | // equivalent to the global ajaxStart event: 586 | this._trigger('start'); 587 | // Set timer for global bitrate progress calculation: 588 | this._bitrateTimer = new this._BitrateTimer(); 589 | } 590 | this._active += 1; 591 | // Initialize the global progress values: 592 | this._loaded += data.uploadedBytes || 0; 593 | this._total += this._getTotal(data.files); 594 | }, 595 | 596 | _onDone: function (result, textStatus, jqXHR, options) { 597 | if (!this._isXHRUpload(options)) { 598 | // Create a progress event for each iframe load: 599 | this._onProgress($.Event('progress', { 600 | lengthComputable: true, 601 | loaded: 1, 602 | total: 1 603 | }), options); 604 | } 605 | options.result = result; 606 | options.textStatus = textStatus; 607 | options.jqXHR = jqXHR; 608 | this._trigger('done', null, options); 609 | }, 610 | 611 | _onFail: function (jqXHR, textStatus, errorThrown, options) { 612 | options.jqXHR = jqXHR; 613 | options.textStatus = textStatus; 614 | options.errorThrown = errorThrown; 615 | this._trigger('fail', null, options); 616 | if (options.recalculateProgress) { 617 | // Remove the failed (error or abort) file upload from 618 | // the global progress calculation: 619 | this._loaded -= options.loaded || options.uploadedBytes || 0; 620 | this._total -= options.total || this._getTotal(options.files); 621 | } 622 | }, 623 | 624 | _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) { 625 | this._active -= 1; 626 | options.textStatus = textStatus; 627 | if (jqXHRorError && jqXHRorError.always) { 628 | options.jqXHR = jqXHRorError; 629 | options.result = jqXHRorResult; 630 | } else { 631 | options.jqXHR = jqXHRorResult; 632 | options.errorThrown = jqXHRorError; 633 | } 634 | this._trigger('always', null, options); 635 | if (this._active === 0) { 636 | // The stop callback is triggered when all uploads have 637 | // been completed, equivalent to the global ajaxStop event: 638 | this._trigger('stop'); 639 | // Reset the global progress values: 640 | this._loaded = this._total = 0; 641 | this._bitrateTimer = null; 642 | } 643 | }, 644 | 645 | _onSend: function (e, data) { 646 | var that = this, 647 | jqXHR, 648 | slot, 649 | pipe, 650 | options = that._getAJAXSettings(data), 651 | send = function (resolve, args) { 652 | that._sending += 1; 653 | // Set timer for bitrate progress calculation: 654 | options._bitrateTimer = new that._BitrateTimer(); 655 | jqXHR = jqXHR || ( 656 | (resolve !== false && 657 | that._trigger('send', e, options) !== false && 658 | (that._chunkedUpload(options) || $.ajax(options))) || 659 | that._getXHRPromise(false, options.context, args) 660 | ).done(function (result, textStatus, jqXHR) { 661 | that._onDone(result, textStatus, jqXHR, options); 662 | }).fail(function (jqXHR, textStatus, errorThrown) { 663 | that._onFail(jqXHR, textStatus, errorThrown, options); 664 | }).always(function (jqXHRorResult, textStatus, jqXHRorError) { 665 | that._sending -= 1; 666 | that._onAlways( 667 | jqXHRorResult, 668 | textStatus, 669 | jqXHRorError, 670 | options 671 | ); 672 | if (options.limitConcurrentUploads && 673 | options.limitConcurrentUploads > that._sending) { 674 | // Start the next queued upload, 675 | // that has not been aborted: 676 | var nextSlot = that._slots.shift(), 677 | isPending; 678 | while (nextSlot) { 679 | // jQuery 1.6 doesn't provide .state(), 680 | // while jQuery 1.8+ removed .isRejected(): 681 | isPending = nextSlot.state ? 682 | nextSlot.state() === 'pending' : 683 | !nextSlot.isRejected(); 684 | if (isPending) { 685 | nextSlot.resolve(); 686 | break; 687 | } 688 | nextSlot = that._slots.shift(); 689 | } 690 | } 691 | }); 692 | return jqXHR; 693 | }; 694 | this._beforeSend(e, options); 695 | if (this.options.sequentialUploads || 696 | (this.options.limitConcurrentUploads && 697 | this.options.limitConcurrentUploads <= this._sending)) { 698 | if (this.options.limitConcurrentUploads > 1) { 699 | slot = $.Deferred(); 700 | this._slots.push(slot); 701 | pipe = slot.pipe(send); 702 | } else { 703 | pipe = (this._sequence = this._sequence.pipe(send, send)); 704 | } 705 | // Return the piped Promise object, enhanced with an abort method, 706 | // which is delegated to the jqXHR object of the current upload, 707 | // and jqXHR callbacks mapped to the equivalent Promise methods: 708 | pipe.abort = function () { 709 | var args = [undefined, 'abort', 'abort']; 710 | if (!jqXHR) { 711 | if (slot) { 712 | slot.rejectWith(pipe, args); 713 | } 714 | return send(false, args); 715 | } 716 | return jqXHR.abort(); 717 | }; 718 | return this._enhancePromise(pipe); 719 | } 720 | return send(); 721 | }, 722 | 723 | _onAdd: function (e, data) { 724 | var that = this, 725 | result = true, 726 | options = $.extend({}, this.options, data), 727 | limit = options.limitMultiFileUploads, 728 | paramName = this._getParamName(options), 729 | paramNameSet, 730 | paramNameSlice, 731 | fileSet, 732 | i; 733 | if (!(options.singleFileUploads || limit) || 734 | !this._isXHRUpload(options)) { 735 | fileSet = [data.files]; 736 | paramNameSet = [paramName]; 737 | } else if (!options.singleFileUploads && limit) { 738 | fileSet = []; 739 | paramNameSet = []; 740 | for (i = 0; i < data.files.length; i += limit) { 741 | fileSet.push(data.files.slice(i, i + limit)); 742 | paramNameSlice = paramName.slice(i, i + limit); 743 | if (!paramNameSlice.length) { 744 | paramNameSlice = paramName; 745 | } 746 | paramNameSet.push(paramNameSlice); 747 | } 748 | } else { 749 | paramNameSet = paramName; 750 | } 751 | data.originalFiles = data.files; 752 | $.each(fileSet || data.files, function (index, element) { 753 | var newData = $.extend({}, data); 754 | newData.files = fileSet ? element : [element]; 755 | newData.paramName = paramNameSet[index]; 756 | newData.submit = function () { 757 | newData.jqXHR = this.jqXHR = 758 | (that._trigger('submit', e, this) !== false) && 759 | that._onSend(e, this); 760 | return this.jqXHR; 761 | }; 762 | return (result = that._trigger('add', e, newData)); 763 | }); 764 | return result; 765 | }, 766 | 767 | _replaceFileInput: function (input) { 768 | var inputClone = input.clone(true); 769 | $('
').append(inputClone)[0].reset(); 770 | // Detaching allows to insert the fileInput on another form 771 | // without loosing the file input value: 772 | input.after(inputClone).detach(); 773 | // Avoid memory leaks with the detached file input: 774 | $.cleanData(input.unbind('remove')); 775 | // Replace the original file input element in the fileInput 776 | // elements set with the clone, which has been copied including 777 | // event handlers: 778 | this.options.fileInput = this.options.fileInput.map(function (i, el) { 779 | if (el === input[0]) { 780 | return inputClone[0]; 781 | } 782 | return el; 783 | }); 784 | // If the widget has been initialized on the file input itself, 785 | // override this.element with the file input clone: 786 | if (input[0] === this.element[0]) { 787 | this.element = inputClone; 788 | } 789 | }, 790 | 791 | _handleFileTreeEntry: function (entry, path) { 792 | var that = this, 793 | dfd = $.Deferred(), 794 | errorHandler = function (e) { 795 | if (e && !e.entry) { 796 | e.entry = entry; 797 | } 798 | // Since $.when returns immediately if one 799 | // Deferred is rejected, we use resolve instead. 800 | // This allows valid files and invalid items 801 | // to be returned together in one set: 802 | dfd.resolve([e]); 803 | }, 804 | dirReader; 805 | path = path || ''; 806 | if (entry.isFile) { 807 | if (entry._file) { 808 | // Workaround for Chrome bug #149735 809 | entry._file.relativePath = path; 810 | dfd.resolve(entry._file); 811 | } else { 812 | entry.file(function (file) { 813 | file.relativePath = path; 814 | dfd.resolve(file); 815 | }, errorHandler); 816 | } 817 | } else if (entry.isDirectory) { 818 | dirReader = entry.createReader(); 819 | dirReader.readEntries(function (entries) { 820 | that._handleFileTreeEntries( 821 | entries, 822 | path + entry.name + '/' 823 | ).done(function (files) { 824 | dfd.resolve(files); 825 | }).fail(errorHandler); 826 | }, errorHandler); 827 | } else { 828 | // Return an empy list for file system items 829 | // other than files or directories: 830 | dfd.resolve([]); 831 | } 832 | return dfd.promise(); 833 | }, 834 | 835 | _handleFileTreeEntries: function (entries, path) { 836 | var that = this; 837 | return $.when.apply( 838 | $, 839 | $.map(entries, function (entry) { 840 | return that._handleFileTreeEntry(entry, path); 841 | }) 842 | ).pipe(function () { 843 | return Array.prototype.concat.apply( 844 | [], 845 | arguments 846 | ); 847 | }); 848 | }, 849 | 850 | _getDroppedFiles: function (dataTransfer) { 851 | dataTransfer = dataTransfer || {}; 852 | var items = dataTransfer.items; 853 | if (items && items.length && (items[0].webkitGetAsEntry || 854 | items[0].getAsEntry)) { 855 | return this._handleFileTreeEntries( 856 | $.map(items, function (item) { 857 | var entry; 858 | if (item.webkitGetAsEntry) { 859 | entry = item.webkitGetAsEntry(); 860 | // Workaround for Chrome bug #149735: 861 | entry._file = item.getAsFile(); 862 | return entry; 863 | } 864 | return item.getAsEntry(); 865 | }) 866 | ); 867 | } 868 | return $.Deferred().resolve( 869 | $.makeArray(dataTransfer.files) 870 | ).promise(); 871 | }, 872 | 873 | _getSingleFileInputFiles: function (fileInput) { 874 | fileInput = $(fileInput); 875 | var entries = fileInput.prop('webkitEntries') || 876 | fileInput.prop('entries'), 877 | files, 878 | value; 879 | if (entries && entries.length) { 880 | return this._handleFileTreeEntries(entries); 881 | } 882 | files = $.makeArray(fileInput.prop('files')); 883 | if (!files.length) { 884 | value = fileInput.prop('value'); 885 | if (!value) { 886 | return $.Deferred().resolve([]).promise(); 887 | } 888 | // If the files property is not available, the browser does not 889 | // support the File API and we add a pseudo File object with 890 | // the input value as name with path information removed: 891 | files = [{name: value.replace(/^.*\\/, '')}]; 892 | } 893 | return $.Deferred().resolve(files).promise(); 894 | }, 895 | 896 | _getFileInputFiles: function (fileInput) { 897 | if (!(fileInput instanceof $) || fileInput.length === 1) { 898 | return this._getSingleFileInputFiles(fileInput); 899 | } 900 | return $.when.apply( 901 | $, 902 | $.map(fileInput, this._getSingleFileInputFiles) 903 | ).pipe(function () { 904 | return Array.prototype.concat.apply( 905 | [], 906 | arguments 907 | ); 908 | }); 909 | }, 910 | 911 | _onChange: function (e) { 912 | var that = e.data.fileupload, 913 | data = { 914 | fileInput: $(e.target), 915 | form: $(e.target.form) 916 | }; 917 | that._getFileInputFiles(data.fileInput).always(function (files) { 918 | data.files = files; 919 | if (that.options.replaceFileInput) { 920 | that._replaceFileInput(data.fileInput); 921 | } 922 | if (that._trigger('change', e, data) !== false) { 923 | that._onAdd(e, data); 924 | } 925 | }); 926 | }, 927 | 928 | _onPaste: function (e) { 929 | var that = e.data.fileupload, 930 | cbd = e.originalEvent.clipboardData, 931 | items = (cbd && cbd.items) || [], 932 | data = {files: []}; 933 | $.each(items, function (index, item) { 934 | var file = item.getAsFile && item.getAsFile(); 935 | if (file) { 936 | data.files.push(file); 937 | } 938 | }); 939 | if (that._trigger('paste', e, data) === false || 940 | that._onAdd(e, data) === false) { 941 | return false; 942 | } 943 | }, 944 | 945 | _onDrop: function (e) { 946 | e.preventDefault(); 947 | var that = e.data.fileupload, 948 | dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer, 949 | data = {}; 950 | that._getDroppedFiles(dataTransfer).always(function (files) { 951 | data.files = files; 952 | if (that._trigger('drop', e, data) !== false) { 953 | that._onAdd(e, data); 954 | } 955 | }); 956 | }, 957 | 958 | _onDragOver: function (e) { 959 | var that = e.data.fileupload, 960 | dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer; 961 | if (that._trigger('dragover', e) === false) { 962 | return false; 963 | } 964 | if (dataTransfer) { 965 | dataTransfer.dropEffect = 'copy'; 966 | } 967 | e.preventDefault(); 968 | }, 969 | 970 | _initEventHandlers: function () { 971 | var ns = this.options.namespace; 972 | if (this._isXHRUpload(this.options)) { 973 | this.options.dropZone 974 | .bind('dragover.' + ns, {fileupload: this}, this._onDragOver) 975 | .bind('drop.' + ns, {fileupload: this}, this._onDrop); 976 | this.options.pasteZone 977 | .bind('paste.' + ns, {fileupload: this}, this._onPaste); 978 | } 979 | this.options.fileInput 980 | .bind('change.' + ns, {fileupload: this}, this._onChange); 981 | }, 982 | 983 | _destroyEventHandlers: function () { 984 | var ns = this.options.namespace; 985 | this.options.dropZone 986 | .unbind('dragover.' + ns, this._onDragOver) 987 | .unbind('drop.' + ns, this._onDrop); 988 | this.options.pasteZone 989 | .unbind('paste.' + ns, this._onPaste); 990 | this.options.fileInput 991 | .unbind('change.' + ns, this._onChange); 992 | }, 993 | 994 | _setOption: function (key, value) { 995 | var refresh = $.inArray(key, this._refreshOptionsList) !== -1; 996 | if (refresh) { 997 | this._destroyEventHandlers(); 998 | } 999 | $.Widget.prototype._setOption.call(this, key, value); 1000 | if (refresh) { 1001 | this._initSpecialOptions(); 1002 | this._initEventHandlers(); 1003 | } 1004 | }, 1005 | 1006 | _initSpecialOptions: function () { 1007 | var options = this.options; 1008 | if (options.fileInput === undefined) { 1009 | options.fileInput = this.element.is('input[type="file"]') ? 1010 | this.element : this.element.find('input[type="file"]'); 1011 | } else if (!(options.fileInput instanceof $)) { 1012 | options.fileInput = $(options.fileInput); 1013 | } 1014 | if (!(options.dropZone instanceof $)) { 1015 | options.dropZone = $(options.dropZone); 1016 | } 1017 | if (!(options.pasteZone instanceof $)) { 1018 | options.pasteZone = $(options.pasteZone); 1019 | } 1020 | }, 1021 | 1022 | _create: function () { 1023 | var options = this.options; 1024 | // Initialize options set via HTML5 data-attributes: 1025 | $.extend(options, $(this.element[0].cloneNode(false)).data()); 1026 | options.namespace = options.namespace || this.widgetName; 1027 | this._initSpecialOptions(); 1028 | this._slots = []; 1029 | this._sequence = this._getXHRPromise(true); 1030 | this._sending = this._active = this._loaded = this._total = 0; 1031 | this._initEventHandlers(); 1032 | }, 1033 | 1034 | destroy: function () { 1035 | this._destroyEventHandlers(); 1036 | $.Widget.prototype.destroy.call(this); 1037 | }, 1038 | 1039 | enable: function () { 1040 | var wasDisabled = false; 1041 | if (this.options.disabled) { 1042 | wasDisabled = true; 1043 | } 1044 | $.Widget.prototype.enable.call(this); 1045 | if (wasDisabled) { 1046 | this._initEventHandlers(); 1047 | } 1048 | }, 1049 | 1050 | disable: function () { 1051 | if (!this.options.disabled) { 1052 | this._destroyEventHandlers(); 1053 | } 1054 | $.Widget.prototype.disable.call(this); 1055 | }, 1056 | 1057 | // This method is exposed to the widget API and allows adding files 1058 | // using the fileupload API. The data parameter accepts an object which 1059 | // must have a files property and can contain additional options: 1060 | // .fileupload('add', {files: filesList}); 1061 | add: function (data) { 1062 | var that = this; 1063 | if (!data || this.options.disabled) { 1064 | return; 1065 | } 1066 | if (data.fileInput && !data.files) { 1067 | this._getFileInputFiles(data.fileInput).always(function (files) { 1068 | data.files = files; 1069 | that._onAdd(null, data); 1070 | }); 1071 | } else { 1072 | data.files = $.makeArray(data.files); 1073 | this._onAdd(null, data); 1074 | } 1075 | }, 1076 | 1077 | // This method is exposed to the widget API and allows sending files 1078 | // using the fileupload API. The data parameter accepts an object which 1079 | // must have a files or fileInput property and can contain additional options: 1080 | // .fileupload('send', {files: filesList}); 1081 | // The method returns a Promise object for the file upload call. 1082 | send: function (data) { 1083 | if (data && !this.options.disabled) { 1084 | if (data.fileInput && !data.files) { 1085 | var that = this, 1086 | dfd = $.Deferred(), 1087 | promise = dfd.promise(), 1088 | jqXHR, 1089 | aborted; 1090 | promise.abort = function () { 1091 | aborted = true; 1092 | if (jqXHR) { 1093 | return jqXHR.abort(); 1094 | } 1095 | dfd.reject(null, 'abort', 'abort'); 1096 | return promise; 1097 | }; 1098 | this._getFileInputFiles(data.fileInput).always( 1099 | function (files) { 1100 | if (aborted) { 1101 | return; 1102 | } 1103 | data.files = files; 1104 | jqXHR = that._onSend(null, data).then( 1105 | function (result, textStatus, jqXHR) { 1106 | dfd.resolve(result, textStatus, jqXHR); 1107 | }, 1108 | function (jqXHR, textStatus, errorThrown) { 1109 | dfd.reject(jqXHR, textStatus, errorThrown); 1110 | } 1111 | ); 1112 | } 1113 | ); 1114 | return this._enhancePromise(promise); 1115 | } 1116 | data.files = $.makeArray(data.files); 1117 | if (data.files.length) { 1118 | return this._onSend(null, data); 1119 | } 1120 | } 1121 | return this._getXHRPromise(false, data && data.context); 1122 | } 1123 | 1124 | }); 1125 | 1126 | })); 1127 | -------------------------------------------------------------------------------- /public/js/jquery.iframe-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Iframe Transport Plugin 1.5 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /*jslint unparam: true, nomen: true */ 13 | /*global define, window, document */ 14 | 15 | (function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define(['jquery'], factory); 20 | } else { 21 | // Browser globals: 22 | factory(window.jQuery); 23 | } 24 | }(function ($) { 25 | 'use strict'; 26 | 27 | // Helper variable to create unique names for the transport iframes: 28 | var counter = 0; 29 | 30 | // The iframe transport accepts three additional options: 31 | // options.fileInput: a jQuery collection of file input fields 32 | // options.paramName: the parameter name for the file form data, 33 | // overrides the name property of the file input field(s), 34 | // can be a string or an array of strings. 35 | // options.formData: an array of objects with name and value properties, 36 | // equivalent to the return data of .serializeArray(), e.g.: 37 | // [{name: 'a', value: 1}, {name: 'b', value: 2}] 38 | $.ajaxTransport('iframe', function (options) { 39 | if (options.async && (options.type === 'POST' || options.type === 'GET')) { 40 | var form, 41 | iframe; 42 | return { 43 | send: function (_, completeCallback) { 44 | form = $(''); 45 | form.attr('accept-charset', options.formAcceptCharset); 46 | // javascript:false as initial iframe src 47 | // prevents warning popups on HTTPS in IE6. 48 | // IE versions below IE8 cannot set the name property of 49 | // elements that have already been added to the DOM, 50 | // so we set the name along with the iframe HTML markup: 51 | iframe = $( 52 | '' 54 | ).bind('load', function () { 55 | var fileInputClones, 56 | paramNames = $.isArray(options.paramName) ? 57 | options.paramName : [options.paramName]; 58 | iframe 59 | .unbind('load') 60 | .bind('load', function () { 61 | var response; 62 | // Wrap in a try/catch block to catch exceptions thrown 63 | // when trying to access cross-domain iframe contents: 64 | try { 65 | response = iframe.contents(); 66 | // Google Chrome and Firefox do not throw an 67 | // exception when calling iframe.contents() on 68 | // cross-domain requests, so we unify the response: 69 | if (!response.length || !response[0].firstChild) { 70 | throw new Error(); 71 | } 72 | } catch (e) { 73 | response = undefined; 74 | } 75 | // The complete callback returns the 76 | // iframe content document as response object: 77 | completeCallback( 78 | 200, 79 | 'success', 80 | {'iframe': response} 81 | ); 82 | // Fix for IE endless progress bar activity bug 83 | // (happens on form submits to iframe targets): 84 | $('') 85 | .appendTo(form); 86 | form.remove(); 87 | }); 88 | form 89 | .prop('target', iframe.prop('name')) 90 | .prop('action', options.url) 91 | .prop('method', options.type); 92 | if (options.formData) { 93 | $.each(options.formData, function (index, field) { 94 | $('') 95 | .prop('name', field.name) 96 | .val(field.value) 97 | .appendTo(form); 98 | }); 99 | } 100 | if (options.fileInput && options.fileInput.length && 101 | options.type === 'POST') { 102 | fileInputClones = options.fileInput.clone(); 103 | // Insert a clone for each file input field: 104 | options.fileInput.after(function (index) { 105 | return fileInputClones[index]; 106 | }); 107 | if (options.paramName) { 108 | options.fileInput.each(function (index) { 109 | $(this).prop( 110 | 'name', 111 | paramNames[index] || options.paramName 112 | ); 113 | }); 114 | } 115 | // Appending the file input fields to the hidden form 116 | // removes them from their original location: 117 | form 118 | .append(options.fileInput) 119 | .prop('enctype', 'multipart/form-data') 120 | // enctype must be set as encoding for IE: 121 | .prop('encoding', 'multipart/form-data'); 122 | } 123 | form.submit(); 124 | // Insert the file input fields at their original location 125 | // by replacing the clones with the originals: 126 | if (fileInputClones && fileInputClones.length) { 127 | options.fileInput.each(function (index, input) { 128 | var clone = $(fileInputClones[index]); 129 | $(input).prop('name', clone.prop('name')); 130 | clone.replaceWith(input); 131 | }); 132 | } 133 | }); 134 | form.append(iframe).appendTo(document.body); 135 | }, 136 | abort: function () { 137 | if (iframe) { 138 | // javascript:false as iframe src aborts the request 139 | // and prevents warning popups on HTTPS in IE6. 140 | // concat is used to avoid the "Script URL" JSLint error: 141 | iframe 142 | .unbind('load') 143 | .prop('src', 'javascript'.concat(':false;')); 144 | } 145 | if (form) { 146 | form.remove(); 147 | } 148 | } 149 | }; 150 | } 151 | }); 152 | 153 | // The iframe transport returns the iframe content document as response. 154 | // The following adds converters from iframe to text, json, html, and script: 155 | $.ajaxSetup({ 156 | converters: { 157 | 'iframe text': function (iframe) { 158 | return $(iframe[0].body).text(); 159 | }, 160 | 'iframe json': function (iframe) { 161 | return $.parseJSON($(iframe[0].body).text()); 162 | }, 163 | 'iframe html': function (iframe) { 164 | return $(iframe[0].body).html(); 165 | }, 166 | 'iframe script': function (iframe) { 167 | return $.globalEval($(iframe[0].body).text()); 168 | } 169 | } 170 | }); 171 | 172 | })); 173 | -------------------------------------------------------------------------------- /public/js/locale.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Plugin Localization Example 6.5.1 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2012, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /*global window */ 13 | 14 | window.locale = { 15 | "fileupload": { 16 | "errors": { 17 | "maxFileSize": "File is too big", 18 | "minFileSize": "File is too small", 19 | "acceptFileTypes": "Filetype not allowed", 20 | "maxNumberOfFiles": "Max number of files exceeded", 21 | "uploadedBytes": "Uploaded bytes exceed file size", 22 | "emptyResult": "Empty file upload result" 23 | }, 24 | "error": "Error", 25 | "start": "Start", 26 | "cancel": "Cancel", 27 | "destroy": "Delete" 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /public/js/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Plugin JS Example 6.7 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2010, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /*jslint nomen: true, unparam: true, regexp: true */ 13 | /*global $, window, document */ 14 | 15 | $(function () { 16 | 'use strict'; 17 | 18 | // Initialize the jQuery File Upload widget: 19 | $('#fileupload').fileupload(); 20 | 21 | // Enable iframe cross-domain access via redirect option: 22 | $('#fileupload').fileupload( 23 | 'option', 24 | 'redirect', 25 | window.location.href.replace( 26 | /\/[^\/]*$/, 27 | '/cors/result.html?%s' 28 | ) 29 | ); 30 | 31 | if (window.location.hostname === 'blueimp.github.com') { 32 | // Demo settings: 33 | $('#fileupload').fileupload('option', { 34 | url: '//jquery-file-upload.appspot.com/', 35 | maxFileSize: 5000000, 36 | acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, 37 | process: [ 38 | { 39 | action: 'load', 40 | fileTypes: /^image\/(gif|jpeg|png)$/, 41 | maxFileSize: 20000000 // 20MB 42 | }, 43 | { 44 | action: 'resize', 45 | maxWidth: 1440, 46 | maxHeight: 900 47 | }, 48 | { 49 | action: 'save' 50 | } 51 | ] 52 | }); 53 | // Upload server status check for browsers with CORS support: 54 | if ($.support.cors) { 55 | $.ajax({ 56 | url: '//jquery-file-upload.appspot.com/', 57 | type: 'HEAD' 58 | }).fail(function () { 59 | $('') 60 | .text('Upload server currently unavailable - ' + 61 | new Date()) 62 | .appendTo('#fileupload'); 63 | }); 64 | } 65 | } else { 66 | // Load existing files: 67 | $('#fileupload').each(function () { 68 | var that = this; 69 | $.getJSON(this.action, function (result) { 70 | if (result && result.length) { 71 | $(that).fileupload('option', 'done') 72 | .call(that, null, {result: result}); 73 | } 74 | }); 75 | }); 76 | } 77 | 78 | }); 79 | -------------------------------------------------------------------------------- /public/js/test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Plugin Test 6.9.5 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2010, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /*jslint nomen: true, unparam: true */ 13 | /*global $, QUnit, document, expect, module, test, asyncTest, start, ok, strictEqual, notStrictEqual */ 14 | 15 | $(function () { 16 | 'use strict'; 17 | 18 | QUnit.done = function () { 19 | // Delete all uploaded files: 20 | var url = $('#fileupload').find('form').prop('action'); 21 | $.getJSON(url, function (files) { 22 | $.each(files, function (index, file) { 23 | $.ajax({ 24 | url: url + '?file=' + encodeURIComponent(file.name), 25 | type: 'DELETE' 26 | }); 27 | }); 28 | }); 29 | }; 30 | 31 | var lifecycle = { 32 | setup: function () { 33 | // Set the .fileupload method to the basic widget method: 34 | $.widget('blueimp.fileupload', $.blueimp.fileupload, {}); 35 | }, 36 | teardown: function () { 37 | // De-initialize the file input plugin: 38 | $('#fileupload:blueimp-fileupload').fileupload('destroy'); 39 | // Remove all remaining event listeners: 40 | $('#fileupload input').unbind(); 41 | $(document).unbind(); 42 | } 43 | }, 44 | lifecycleUI = { 45 | setup: function () { 46 | // Set the .fileupload method to the UI widget method: 47 | $.widget('blueimpUI.fileupload', $.blueimpUI.fileupload, {}); 48 | }, 49 | teardown: function () { 50 | // De-initialize the file input plugin: 51 | $('#fileupload:blueimpUI-fileupload').fileupload('destroy'); 52 | // Remove all remaining event listeners: 53 | $('#fileupload input, #fileupload button').unbind(); 54 | $(document).unbind(); 55 | } 56 | }; 57 | 58 | module('Initialization', lifecycle); 59 | 60 | test('Widget initialization', function () { 61 | ok($('#fileupload').fileupload().data('fileupload')); 62 | }); 63 | 64 | test('Data attribute options', function () { 65 | $('#fileupload').attr('data-url', 'http://example.org'); 66 | $('#fileupload').fileupload(); 67 | strictEqual( 68 | $('#fileupload').fileupload('option', 'url'), 69 | 'http://example.org' 70 | ); 71 | }); 72 | 73 | test('File input initialization', function () { 74 | var fu = $('#fileupload').fileupload(); 75 | ok( 76 | fu.fileupload('option', 'fileInput').length, 77 | 'File input field inside of the widget' 78 | ); 79 | ok( 80 | fu.fileupload('option', 'fileInput').length, 81 | 'Widget element as file input field' 82 | ); 83 | }); 84 | 85 | test('Drop zone initialization', function () { 86 | ok($('#fileupload').fileupload() 87 | .fileupload('option', 'dropZone').length); 88 | }); 89 | 90 | test('Paste zone initialization', function () { 91 | ok($('#fileupload').fileupload() 92 | .fileupload('option', 'pasteZone').length); 93 | }); 94 | 95 | test('Event listeners initialization', function () { 96 | expect( 97 | $.support.xhrFormDataFileUpload ? 4 : 1 98 | ); 99 | var eo = {originalEvent: {}}, 100 | fu = $('#fileupload').fileupload({ 101 | dragover: function () { 102 | ok(true, 'Triggers dragover callback'); 103 | return false; 104 | }, 105 | drop: function () { 106 | ok(true, 'Triggers drop callback'); 107 | return false; 108 | }, 109 | paste: function () { 110 | ok(true, 'Triggers paste callback'); 111 | return false; 112 | }, 113 | change: function () { 114 | ok(true, 'Triggers change callback'); 115 | return false; 116 | } 117 | }), 118 | fileInput = fu.fileupload('option', 'fileInput'), 119 | dropZone = fu.fileupload('option', 'dropZone'), 120 | pasteZone = fu.fileupload('option', 'pasteZone'); 121 | fileInput.trigger($.Event('change', eo)); 122 | dropZone.trigger($.Event('dragover', eo)); 123 | dropZone.trigger($.Event('drop', eo)); 124 | pasteZone.trigger($.Event('paste', eo)); 125 | }); 126 | 127 | module('API', lifecycle); 128 | 129 | test('destroy', function () { 130 | expect(4); 131 | var eo = {originalEvent: {}}, 132 | options = { 133 | dragover: function () { 134 | ok(true, 'Triggers dragover callback'); 135 | return false; 136 | }, 137 | drop: function () { 138 | ok(true, 'Triggers drop callback'); 139 | return false; 140 | }, 141 | paste: function () { 142 | ok(true, 'Triggers paste callback'); 143 | return false; 144 | }, 145 | change: function () { 146 | ok(true, 'Triggers change callback'); 147 | return false; 148 | } 149 | }, 150 | fu = $('#fileupload').fileupload(options), 151 | fileInput = fu.fileupload('option', 'fileInput'), 152 | dropZone = fu.fileupload('option', 'dropZone'), 153 | pasteZone = fu.fileupload('option', 'pasteZone'); 154 | dropZone.bind('dragover', options.dragover); 155 | dropZone.bind('drop', options.drop); 156 | pasteZone.bind('paste', options.paste); 157 | fileInput.bind('change', options.change); 158 | fu.fileupload('destroy'); 159 | fileInput.trigger($.Event('change', eo)); 160 | dropZone.trigger($.Event('dragover', eo)); 161 | dropZone.trigger($.Event('drop', eo)); 162 | pasteZone.trigger($.Event('paste', eo)); 163 | }); 164 | 165 | test('disable/enable', function () { 166 | expect( 167 | $.support.xhrFormDataFileUpload ? 4 : 1 168 | ); 169 | var eo = {originalEvent: {}}, 170 | fu = $('#fileupload').fileupload({ 171 | dragover: function () { 172 | ok(true, 'Triggers dragover callback'); 173 | return false; 174 | }, 175 | drop: function () { 176 | ok(true, 'Triggers drop callback'); 177 | return false; 178 | }, 179 | paste: function () { 180 | ok(true, 'Triggers paste callback'); 181 | return false; 182 | }, 183 | change: function () { 184 | ok(true, 'Triggers change callback'); 185 | return false; 186 | } 187 | }), 188 | fileInput = fu.fileupload('option', 'fileInput'), 189 | dropZone = fu.fileupload('option', 'dropZone'), 190 | pasteZone = fu.fileupload('option', 'pasteZone'); 191 | fu.fileupload('disable'); 192 | fileInput.trigger($.Event('change', eo)); 193 | dropZone.trigger($.Event('dragover', eo)); 194 | dropZone.trigger($.Event('drop', eo)); 195 | pasteZone.trigger($.Event('paste', eo)); 196 | fu.fileupload('enable'); 197 | fileInput.trigger($.Event('change', eo)); 198 | dropZone.trigger($.Event('dragover', eo)); 199 | dropZone.trigger($.Event('drop', eo)); 200 | pasteZone.trigger($.Event('paste', eo)); 201 | }); 202 | 203 | test('option', function () { 204 | expect( 205 | $.support.xhrFormDataFileUpload ? 10 : 7 206 | ); 207 | var eo = {originalEvent: {}}, 208 | fu = $('#fileupload').fileupload({ 209 | dragover: function () { 210 | ok(true, 'Triggers dragover callback'); 211 | return false; 212 | }, 213 | drop: function () { 214 | ok(true, 'Triggers drop callback'); 215 | return false; 216 | }, 217 | paste: function () { 218 | ok(true, 'Triggers paste callback'); 219 | return false; 220 | }, 221 | change: function () { 222 | ok(true, 'Triggers change callback'); 223 | return false; 224 | } 225 | }), 226 | fileInput = fu.fileupload('option', 'fileInput'), 227 | dropZone = fu.fileupload('option', 'dropZone'), 228 | pasteZone = fu.fileupload('option', 'pasteZone'); 229 | fu.fileupload('option', 'fileInput', null); 230 | fu.fileupload('option', 'dropZone', null); 231 | fu.fileupload('option', 'pasteZone', null); 232 | fileInput.trigger($.Event('change', eo)); 233 | dropZone.trigger($.Event('dragover', eo)); 234 | dropZone.trigger($.Event('drop', eo)); 235 | pasteZone.trigger($.Event('paste', eo)); 236 | fu.fileupload('option', 'dropZone', 'body'); 237 | strictEqual( 238 | fu.fileupload('option', 'dropZone')[0], 239 | document.body, 240 | 'Allow a query string as parameter for the dropZone option' 241 | ); 242 | fu.fileupload('option', 'dropZone', document); 243 | strictEqual( 244 | fu.fileupload('option', 'dropZone')[0], 245 | document, 246 | 'Allow a document element as parameter for the dropZone option' 247 | ); 248 | fu.fileupload('option', 'pasteZone', 'body'); 249 | strictEqual( 250 | fu.fileupload('option', 'pasteZone')[0], 251 | document.body, 252 | 'Allow a query string as parameter for the pasteZone option' 253 | ); 254 | fu.fileupload('option', 'pasteZone', document); 255 | strictEqual( 256 | fu.fileupload('option', 'pasteZone')[0], 257 | document, 258 | 'Allow a document element as parameter for the pasteZone option' 259 | ); 260 | fu.fileupload('option', 'fileInput', ':file'); 261 | strictEqual( 262 | fu.fileupload('option', 'fileInput')[0], 263 | $(':file')[0], 264 | 'Allow a query string as parameter for the fileInput option' 265 | ); 266 | fu.fileupload('option', 'fileInput', $(':file')[0]); 267 | strictEqual( 268 | fu.fileupload('option', 'fileInput')[0], 269 | $(':file')[0], 270 | 'Allow a document element as parameter for the fileInput option' 271 | ); 272 | fu.fileupload('option', 'fileInput', fileInput); 273 | fu.fileupload('option', 'dropZone', dropZone); 274 | fu.fileupload('option', 'pasteZone', pasteZone); 275 | fileInput.trigger($.Event('change', eo)); 276 | dropZone.trigger($.Event('dragover', eo)); 277 | dropZone.trigger($.Event('drop', eo)); 278 | pasteZone.trigger($.Event('paste', eo)); 279 | }); 280 | 281 | asyncTest('add', function () { 282 | expect(2); 283 | var param = {files: [{name: 'test'}]}; 284 | $('#fileupload').fileupload({ 285 | add: function (e, data) { 286 | strictEqual( 287 | data.files[0].name, 288 | param.files[0].name, 289 | 'Triggers add callback' 290 | ); 291 | } 292 | }).fileupload('add', param).fileupload( 293 | 'option', 294 | 'add', 295 | function (e, data) { 296 | data.submit().complete(function () { 297 | ok(true, 'data.submit() Returns a jqXHR object'); 298 | start(); 299 | }); 300 | } 301 | ).fileupload('add', param); 302 | }); 303 | 304 | asyncTest('send', function () { 305 | expect(3); 306 | var param = {files: [{name: 'test'}]}; 307 | $('#fileupload').fileupload({ 308 | send: function (e, data) { 309 | strictEqual( 310 | data.files[0].name, 311 | 'test', 312 | 'Triggers send callback' 313 | ); 314 | } 315 | }).fileupload('send', param).fail(function () { 316 | ok(true, 'Allows to abort the request'); 317 | }).complete(function () { 318 | ok(true, 'Returns a jqXHR object'); 319 | start(); 320 | }).abort(); 321 | }); 322 | 323 | module('Callbacks', lifecycle); 324 | 325 | asyncTest('add', function () { 326 | expect(1); 327 | var param = {files: [{name: 'test'}]}; 328 | $('#fileupload').fileupload({ 329 | add: function (e, data) { 330 | ok(true, 'Triggers add callback'); 331 | start(); 332 | } 333 | }).fileupload('add', param); 334 | }); 335 | 336 | asyncTest('submit', function () { 337 | expect(1); 338 | var param = {files: [{name: 'test'}]}; 339 | $('#fileupload').fileupload({ 340 | submit: function (e, data) { 341 | ok(true, 'Triggers submit callback'); 342 | start(); 343 | return false; 344 | } 345 | }).fileupload('add', param); 346 | }); 347 | 348 | asyncTest('send', function () { 349 | expect(1); 350 | var param = {files: [{name: 'test'}]}; 351 | $('#fileupload').fileupload({ 352 | send: function (e, data) { 353 | ok(true, 'Triggers send callback'); 354 | start(); 355 | return false; 356 | } 357 | }).fileupload('send', param); 358 | }); 359 | 360 | asyncTest('done', function () { 361 | expect(1); 362 | var param = {files: [{name: 'test'}]}; 363 | $('#fileupload').fileupload({ 364 | done: function (e, data) { 365 | ok(true, 'Triggers done callback'); 366 | start(); 367 | } 368 | }).fileupload('send', param); 369 | }); 370 | 371 | asyncTest('fail', function () { 372 | expect(1); 373 | var param = {files: [{name: 'test'}]}, 374 | fu = $('#fileupload').fileupload({ 375 | url: '404', 376 | fail: function (e, data) { 377 | ok(true, 'Triggers fail callback'); 378 | start(); 379 | } 380 | }); 381 | fu.data('fileupload')._isXHRUpload = function () { 382 | return true; 383 | }; 384 | fu.fileupload('send', param); 385 | }); 386 | 387 | asyncTest('always', function () { 388 | expect(2); 389 | var param = {files: [{name: 'test'}]}, 390 | counter = 0, 391 | fu = $('#fileupload').fileupload({ 392 | always: function (e, data) { 393 | ok(true, 'Triggers always callback'); 394 | if (counter === 1) { 395 | start(); 396 | } else { 397 | counter += 1; 398 | } 399 | } 400 | }); 401 | fu.data('fileupload')._isXHRUpload = function () { 402 | return true; 403 | }; 404 | fu.fileupload('add', param).fileupload( 405 | 'option', 406 | 'url', 407 | '404' 408 | ).fileupload('add', param); 409 | }); 410 | 411 | asyncTest('progress', function () { 412 | expect(1); 413 | var param = {files: [{name: 'test'}]}, 414 | counter = 0; 415 | $('#fileupload').fileupload({ 416 | forceIframeTransport: true, 417 | progress: function (e, data) { 418 | ok(true, 'Triggers progress callback'); 419 | if (counter === 0) { 420 | start(); 421 | } else { 422 | counter += 1; 423 | } 424 | } 425 | }).fileupload('send', param); 426 | }); 427 | 428 | asyncTest('progressall', function () { 429 | expect(1); 430 | var param = {files: [{name: 'test'}]}, 431 | counter = 0; 432 | $('#fileupload').fileupload({ 433 | forceIframeTransport: true, 434 | progressall: function (e, data) { 435 | ok(true, 'Triggers progressall callback'); 436 | if (counter === 0) { 437 | start(); 438 | } else { 439 | counter += 1; 440 | } 441 | } 442 | }).fileupload('send', param); 443 | }); 444 | 445 | asyncTest('start', function () { 446 | expect(1); 447 | var param = {files: [{name: '1'}, {name: '2'}]}, 448 | active = 0; 449 | $('#fileupload').fileupload({ 450 | send: function (e, data) { 451 | active += 1; 452 | }, 453 | start: function (e, data) { 454 | ok(!active, 'Triggers start callback before uploads'); 455 | start(); 456 | } 457 | }).fileupload('send', param); 458 | }); 459 | 460 | asyncTest('stop', function () { 461 | expect(1); 462 | var param = {files: [{name: '1'}, {name: '2'}]}, 463 | active = 0; 464 | $('#fileupload').fileupload({ 465 | send: function (e, data) { 466 | active += 1; 467 | }, 468 | always: function (e, data) { 469 | active -= 1; 470 | }, 471 | stop: function (e, data) { 472 | ok(!active, 'Triggers stop callback after uploads'); 473 | start(); 474 | } 475 | }).fileupload('send', param); 476 | }); 477 | 478 | test('change', function () { 479 | var fu = $('#fileupload').fileupload(), 480 | fuo = fu.data('fileupload'), 481 | fileInput = fu.fileupload('option', 'fileInput'); 482 | expect(2); 483 | fu.fileupload({ 484 | change: function (e, data) { 485 | ok(true, 'Triggers change callback'); 486 | strictEqual( 487 | data.files.length, 488 | 0, 489 | 'Returns empty files list' 490 | ); 491 | }, 492 | add: $.noop 493 | }); 494 | fuo._onChange({ 495 | data: {fileupload: fuo}, 496 | target: fileInput[0] 497 | }); 498 | }); 499 | 500 | test('paste', function () { 501 | var fu = $('#fileupload').fileupload(), 502 | fuo = fu.data('fileupload'); 503 | expect(1); 504 | fu.fileupload({ 505 | paste: function (e, data) { 506 | ok(true, 'Triggers paste callback'); 507 | }, 508 | add: $.noop 509 | }); 510 | fuo._onPaste({ 511 | data: {fileupload: fuo}, 512 | originalEvent: {clipboardData: {}}, 513 | preventDefault: $.noop 514 | }); 515 | }); 516 | 517 | test('drop', function () { 518 | var fu = $('#fileupload').fileupload(), 519 | fuo = fu.data('fileupload'); 520 | expect(1); 521 | fu.fileupload({ 522 | drop: function (e, data) { 523 | ok(true, 'Triggers drop callback'); 524 | }, 525 | add: $.noop 526 | }); 527 | fuo._onDrop({ 528 | data: {fileupload: fuo}, 529 | originalEvent: {dataTransfer: {}}, 530 | preventDefault: $.noop 531 | }); 532 | }); 533 | 534 | test('dragover', function () { 535 | var fu = $('#fileupload').fileupload(), 536 | fuo = fu.data('fileupload'); 537 | expect(1); 538 | fu.fileupload({ 539 | dragover: function (e, data) { 540 | ok(true, 'Triggers dragover callback'); 541 | }, 542 | add: $.noop 543 | }); 544 | fuo._onDragOver({ 545 | data: {fileupload: fuo}, 546 | originalEvent: {dataTransfer: {}}, 547 | preventDefault: $.noop 548 | }); 549 | }); 550 | 551 | module('Options', lifecycle); 552 | 553 | test('paramName', function () { 554 | expect(1); 555 | var param = {files: [{name: 'test'}]}; 556 | $('#fileupload').fileupload({ 557 | paramName: null, 558 | send: function (e, data) { 559 | strictEqual( 560 | data.paramName[0], 561 | data.fileInput.prop('name'), 562 | 'Takes paramName from file input field if not set' 563 | ); 564 | return false; 565 | } 566 | }).fileupload('send', param); 567 | }); 568 | 569 | test('url', function () { 570 | expect(1); 571 | var param = {files: [{name: 'test'}]}; 572 | $('#fileupload').fileupload({ 573 | url: null, 574 | send: function (e, data) { 575 | strictEqual( 576 | data.url, 577 | $(data.fileInput.prop('form')).prop('action'), 578 | 'Takes url from form action if not set' 579 | ); 580 | return false; 581 | } 582 | }).fileupload('send', param); 583 | }); 584 | 585 | test('type', function () { 586 | expect(2); 587 | var param = {files: [{name: 'test'}]}; 588 | $('#fileupload').fileupload({ 589 | type: null, 590 | send: function (e, data) { 591 | strictEqual( 592 | data.type, 593 | 'POST', 594 | 'Request type is "POST" if not set to "PUT"' 595 | ); 596 | return false; 597 | } 598 | }).fileupload('send', param); 599 | $('#fileupload').fileupload({ 600 | type: 'PUT', 601 | send: function (e, data) { 602 | strictEqual( 603 | data.type, 604 | 'PUT', 605 | 'Request type is "PUT" if set to "PUT"' 606 | ); 607 | return false; 608 | } 609 | }).fileupload('send', param); 610 | }); 611 | 612 | test('replaceFileInput', function () { 613 | var fu = $('#fileupload').fileupload(), 614 | fuo = fu.data('fileupload'), 615 | fileInput = fu.fileupload('option', 'fileInput'), 616 | fileInputElement = fileInput[0]; 617 | expect(2); 618 | fu.fileupload({ 619 | replaceFileInput: false, 620 | change: function (e, data) { 621 | strictEqual( 622 | fu.fileupload('option', 'fileInput')[0], 623 | fileInputElement, 624 | 'Keeps file input with replaceFileInput: false' 625 | ); 626 | }, 627 | add: $.noop 628 | }); 629 | fuo._onChange({ 630 | data: {fileupload: fuo}, 631 | target: fileInput[0] 632 | }); 633 | fu.fileupload({ 634 | replaceFileInput: true, 635 | change: function (e, data) { 636 | notStrictEqual( 637 | fu.fileupload('option', 'fileInput')[0], 638 | fileInputElement, 639 | 'Replaces file input with replaceFileInput: true' 640 | ); 641 | }, 642 | add: $.noop 643 | }); 644 | fuo._onChange({ 645 | data: {fileupload: fuo}, 646 | target: fileInput[0] 647 | }); 648 | }); 649 | 650 | asyncTest('forceIframeTransport', function () { 651 | expect(1); 652 | var param = {files: [{name: 'test'}]}; 653 | $('#fileupload').fileupload({ 654 | forceIframeTransport: true, 655 | done: function (e, data) { 656 | strictEqual( 657 | data.dataType.substr(0, 6), 658 | 'iframe', 659 | 'Iframe Transport is used' 660 | ); 661 | start(); 662 | } 663 | }).fileupload('send', param); 664 | }); 665 | 666 | test('singleFileUploads', function () { 667 | expect(3); 668 | var fu = $('#fileupload').fileupload(), 669 | param = {files: [{name: '1'}, {name: '2'}]}, 670 | index = 1; 671 | fu.data('fileupload')._isXHRUpload = function () { 672 | return true; 673 | }; 674 | $('#fileupload').fileupload({ 675 | singleFileUploads: true, 676 | add: function (e, data) { 677 | ok(true, 'Triggers callback number ' + index.toString()); 678 | index += 1; 679 | } 680 | }).fileupload('add', param).fileupload( 681 | 'option', 682 | 'singleFileUploads', 683 | false 684 | ).fileupload('add', param); 685 | }); 686 | 687 | test('limitMultiFileUploads', function () { 688 | expect(3); 689 | var fu = $('#fileupload').fileupload(), 690 | param = {files: [ 691 | {name: '1'}, 692 | {name: '2'}, 693 | {name: '3'}, 694 | {name: '4'}, 695 | {name: '5'} 696 | ]}, 697 | index = 1; 698 | fu.data('fileupload')._isXHRUpload = function () { 699 | return true; 700 | }; 701 | $('#fileupload').fileupload({ 702 | singleFileUploads: false, 703 | limitMultiFileUploads: 2, 704 | add: function (e, data) { 705 | ok(true, 'Triggers callback number ' + index.toString()); 706 | index += 1; 707 | } 708 | }).fileupload('add', param); 709 | }); 710 | 711 | asyncTest('sequentialUploads', function () { 712 | expect(6); 713 | var param = {files: [ 714 | {name: '1'}, 715 | {name: '2'}, 716 | {name: '3'}, 717 | {name: '4'}, 718 | {name: '5'}, 719 | {name: '6'} 720 | ]}, 721 | addIndex = 0, 722 | sendIndex = 0, 723 | loadIndex = 0, 724 | fu = $('#fileupload').fileupload({ 725 | sequentialUploads: true, 726 | add: function (e, data) { 727 | addIndex += 1; 728 | if (addIndex === 4) { 729 | data.submit().abort(); 730 | } else { 731 | data.submit(); 732 | } 733 | }, 734 | send: function (e, data) { 735 | sendIndex += 1; 736 | }, 737 | done: function (e, data) { 738 | loadIndex += 1; 739 | strictEqual(sendIndex, loadIndex, 'upload in order'); 740 | }, 741 | fail: function (e, data) { 742 | strictEqual(data.errorThrown, 'abort', 'upload aborted'); 743 | }, 744 | stop: function (e) { 745 | start(); 746 | } 747 | }); 748 | fu.data('fileupload')._isXHRUpload = function () { 749 | return true; 750 | }; 751 | fu.fileupload('add', param); 752 | }); 753 | 754 | asyncTest('limitConcurrentUploads', function () { 755 | expect(12); 756 | var param = {files: [ 757 | {name: '1'}, 758 | {name: '2'}, 759 | {name: '3'}, 760 | {name: '4'}, 761 | {name: '5'}, 762 | {name: '6'}, 763 | {name: '7'}, 764 | {name: '8'}, 765 | {name: '9'}, 766 | {name: '10'}, 767 | {name: '11'}, 768 | {name: '12'} 769 | ]}, 770 | addIndex = 0, 771 | sendIndex = 0, 772 | loadIndex = 0, 773 | fu = $('#fileupload').fileupload({ 774 | limitConcurrentUploads: 3, 775 | add: function (e, data) { 776 | addIndex += 1; 777 | if (addIndex === 4) { 778 | data.submit().abort(); 779 | } else { 780 | data.submit(); 781 | } 782 | }, 783 | send: function (e, data) { 784 | sendIndex += 1; 785 | }, 786 | done: function (e, data) { 787 | loadIndex += 1; 788 | ok(sendIndex - loadIndex < 3); 789 | }, 790 | fail: function (e, data) { 791 | strictEqual(data.errorThrown, 'abort', 'upload aborted'); 792 | }, 793 | stop: function (e) { 794 | start(); 795 | } 796 | }); 797 | fu.data('fileupload')._isXHRUpload = function () { 798 | return true; 799 | }; 800 | fu.fileupload('add', param); 801 | }); 802 | 803 | if ($.support.xhrFileUpload) { 804 | asyncTest('multipart', function () { 805 | expect(4); 806 | var param = {files: [{ 807 | name: 'test.png', 808 | size: 123, 809 | type: 'image/png' 810 | }]}, 811 | fu = $('#fileupload').fileupload({ 812 | multipart: false, 813 | always: function (e, data) { 814 | strictEqual( 815 | data.contentType, 816 | param.files[0].type, 817 | 'non-multipart upload sets file type as contentType' 818 | ); 819 | strictEqual( 820 | data.headers['X-File-Name'], 821 | param.files[0].name, 822 | 'non-multipart upload sets X-File-Name header' 823 | ); 824 | strictEqual( 825 | data.headers['X-File-Type'], 826 | param.files[0].type, 827 | 'non-multipart upload sets X-File-Type header' 828 | ); 829 | strictEqual( 830 | data.headers['X-File-Size'], 831 | param.files[0].size, 832 | 'non-multipart upload sets X-File-Size header' 833 | ); 834 | start(); 835 | } 836 | }); 837 | fu.fileupload('send', param); 838 | }); 839 | } 840 | 841 | module('UI Initialization', lifecycleUI); 842 | 843 | test('Widget initialization', function () { 844 | ok($('#fileupload').fileupload().data('fileupload')); 845 | ok( 846 | $('#fileupload').fileupload('option', 'uploadTemplate').length, 847 | 'Initialized upload template' 848 | ); 849 | ok( 850 | $('#fileupload').fileupload('option', 'downloadTemplate').length, 851 | 'Initialized download template' 852 | ); 853 | }); 854 | 855 | test('Buttonbar event listeners', function () { 856 | var buttonbar = $('#fileupload .fileupload-buttonbar'), 857 | files = [{name: 'test'}]; 858 | expect(4); 859 | $('#fileupload').fileupload({ 860 | send: function (e, data) { 861 | ok(true, 'Started file upload via global start button'); 862 | }, 863 | fail: function (e, data) { 864 | ok(true, 'Canceled file upload via global cancel button'); 865 | data.context.remove(); 866 | }, 867 | destroy: function (e, data) { 868 | ok(true, 'Delete action called via global delete button'); 869 | } 870 | }); 871 | $('#fileupload').fileupload('add', {files: files}); 872 | buttonbar.find('.cancel').click(); 873 | $('#fileupload').fileupload('add', {files: files}); 874 | buttonbar.find('.start').click(); 875 | buttonbar.find('.cancel').click(); 876 | $('#fileupload').data('fileupload')._renderDownload(files) 877 | .appendTo($('#fileupload .files')).show() 878 | .find('.delete input').click(); 879 | buttonbar.find('.delete').click(); 880 | }); 881 | 882 | module('UI API', lifecycleUI); 883 | 884 | test('destroy', function () { 885 | var buttonbar = $('#fileupload .fileupload-buttonbar'), 886 | files = [{name: 'test'}]; 887 | expect(1); 888 | $('#fileupload').fileupload({ 889 | send: function (e, data) { 890 | ok(true, 'This test should not run'); 891 | return false; 892 | } 893 | }) 894 | .fileupload('add', {files: files}) 895 | .fileupload('destroy'); 896 | buttonbar.find('.start').click(function () { 897 | ok(true, 'Clicked global start button'); 898 | return false; 899 | }).click(); 900 | }); 901 | 902 | test('disable/enable', function () { 903 | var buttonbar = $('#fileupload .fileupload-buttonbar'); 904 | $('#fileupload').fileupload(); 905 | $('#fileupload').fileupload('disable'); 906 | strictEqual( 907 | buttonbar.find('input[type=file], button').not(':disabled').length, 908 | 0, 909 | 'Disables the buttonbar buttons' 910 | ); 911 | $('#fileupload').fileupload('enable'); 912 | strictEqual( 913 | buttonbar.find('input[type=file], button').not(':disabled').length, 914 | 4, 915 | 'Enables the buttonbar buttons' 916 | ); 917 | }); 918 | 919 | module('UI Callbacks', lifecycleUI); 920 | 921 | test('destroy', function () { 922 | expect(3); 923 | $('#fileupload').fileupload({ 924 | destroy: function (e, data) { 925 | ok(true, 'Triggers destroy callback'); 926 | strictEqual( 927 | data.url, 928 | 'test', 929 | 'Passes over deletion url parameter' 930 | ); 931 | strictEqual( 932 | data.type, 933 | 'DELETE', 934 | 'Passes over deletion request type parameter' 935 | ); 936 | } 937 | }); 938 | $('#fileupload').data('fileupload')._renderDownload([{ 939 | name: 'test', 940 | delete_url: 'test', 941 | delete_type: 'DELETE' 942 | }]).appendTo($('#fileupload .files')).show() 943 | .find('.delete input').click(); 944 | $('#fileupload .fileupload-buttonbar .delete').click(); 945 | }); 946 | 947 | asyncTest('added', function () { 948 | expect(1); 949 | var param = {files: [{name: 'test'}]}; 950 | $('#fileupload').fileupload({ 951 | added: function (e, data) { 952 | start(); 953 | strictEqual( 954 | data.files[0].name, 955 | param.files[0].name, 956 | 'Triggers added callback' 957 | ); 958 | }, 959 | send: function () { 960 | return false; 961 | } 962 | }).fileupload('add', param); 963 | }); 964 | 965 | asyncTest('started', function () { 966 | expect(1); 967 | var param = {files: [{name: 'test'}]}; 968 | $('#fileupload').fileupload({ 969 | started: function (e) { 970 | start(); 971 | ok('Triggers started callback'); 972 | return false; 973 | }, 974 | sent: function (e, data) { 975 | return false; 976 | } 977 | }).fileupload('send', param); 978 | }); 979 | 980 | asyncTest('sent', function () { 981 | expect(1); 982 | var param = {files: [{name: 'test'}]}; 983 | $('#fileupload').fileupload({ 984 | sent: function (e, data) { 985 | start(); 986 | strictEqual( 987 | data.files[0].name, 988 | param.files[0].name, 989 | 'Triggers sent callback' 990 | ); 991 | return false; 992 | } 993 | }).fileupload('send', param); 994 | }); 995 | 996 | asyncTest('completed', function () { 997 | expect(1); 998 | var param = {files: [{name: 'test'}]}; 999 | $('#fileupload').fileupload({ 1000 | completed: function (e, data) { 1001 | start(); 1002 | ok('Triggers completed callback'); 1003 | return false; 1004 | } 1005 | }).fileupload('send', param); 1006 | }); 1007 | 1008 | asyncTest('failed', function () { 1009 | expect(1); 1010 | var param = {files: [{name: 'test'}]}; 1011 | $('#fileupload').fileupload({ 1012 | failed: function (e, data) { 1013 | start(); 1014 | ok('Triggers failed callback'); 1015 | return false; 1016 | } 1017 | }).fileupload('send', param).abort(); 1018 | }); 1019 | 1020 | asyncTest('stopped', function () { 1021 | expect(1); 1022 | var param = {files: [{name: 'test'}]}; 1023 | $('#fileupload').fileupload({ 1024 | stopped: function (e, data) { 1025 | start(); 1026 | ok('Triggers stopped callback'); 1027 | return false; 1028 | } 1029 | }).fileupload('send', param); 1030 | }); 1031 | 1032 | asyncTest('destroyed', function () { 1033 | expect(1); 1034 | $('#fileupload').fileupload({ 1035 | destroyed: function (e, data) { 1036 | start(); 1037 | ok(true, 'Triggers destroyed callback'); 1038 | } 1039 | }); 1040 | $('#fileupload').data('fileupload')._renderDownload([{ 1041 | name: 'test', 1042 | delete_url: 'test', 1043 | delete_type: 'DELETE' 1044 | }]).appendTo($('#fileupload .files')).show() 1045 | .find('.delete input').click(); 1046 | $('#fileupload .fileupload-buttonbar .delete').click(); 1047 | }); 1048 | 1049 | module('UI Options', lifecycleUI); 1050 | 1051 | test('autoUpload', function () { 1052 | expect(1); 1053 | $('#fileupload') 1054 | .fileupload({ 1055 | autoUpload: true, 1056 | send: function (e, data) { 1057 | ok(true, 'Started file upload automatically'); 1058 | return false; 1059 | } 1060 | }) 1061 | .fileupload('add', {files: [{name: 'test'}]}) 1062 | .fileupload('option', 'autoUpload', false) 1063 | .fileupload('add', {files: [{name: 'test'}]}); 1064 | }); 1065 | 1066 | test('maxNumberOfFiles', function () { 1067 | expect(4); 1068 | var addIndex = 0, 1069 | sendIndex = 0; 1070 | $('#fileupload') 1071 | .fileupload({ 1072 | autoUpload: true, 1073 | maxNumberOfFiles: 1, 1074 | singleFileUploads: false, 1075 | send: function (e, data) { 1076 | strictEqual( 1077 | sendIndex += 1, 1078 | addIndex 1079 | ); 1080 | }, 1081 | progress: $.noop, 1082 | progressall: $.noop, 1083 | done: $.noop, 1084 | stop: $.noop 1085 | }) 1086 | .fileupload('add', {files: [{name: (addIndex += 1)}]}) 1087 | .fileupload('add', {files: [{name: 'test'}]}) 1088 | .fileupload('option', 'maxNumberOfFiles', 1) 1089 | .fileupload('add', {files: [{name: 1}, {name: 2}]}) 1090 | .fileupload({ 1091 | maxNumberOfFiles: 1, 1092 | send: function (e, data) { 1093 | strictEqual( 1094 | sendIndex += 1, 1095 | addIndex 1096 | ); 1097 | return false; 1098 | } 1099 | }) 1100 | .fileupload('add', {files: [{name: (addIndex += 1)}]}) 1101 | .fileupload('add', {files: [{name: (addIndex += 1)}]}) 1102 | .fileupload({ 1103 | maxNumberOfFiles: 0, 1104 | send: function (e, data) { 1105 | ok( 1106 | !$.blueimpUI.fileupload.prototype.options 1107 | .send.call(this, e, data) 1108 | ); 1109 | return false; 1110 | } 1111 | }) 1112 | .fileupload('send', {files: [{name: 'test'}]}); 1113 | }); 1114 | 1115 | test('maxFileSize', function () { 1116 | expect(3); 1117 | var addIndex = 0, 1118 | sendIndex = 0; 1119 | $('#fileupload') 1120 | .fileupload({ 1121 | autoUpload: true, 1122 | maxFileSize: 1000, 1123 | send: function (e, data) { 1124 | strictEqual( 1125 | sendIndex += 1, 1126 | addIndex 1127 | ); 1128 | return false; 1129 | } 1130 | }) 1131 | .fileupload('add', {files: [{ 1132 | name: (addIndex += 1) 1133 | }]}) 1134 | .fileupload('add', {files: [{ 1135 | name: (addIndex += 1), 1136 | size: 999 1137 | }]}) 1138 | .fileupload('add', {files: [{ 1139 | name: 'test', 1140 | size: 1001 1141 | }]}) 1142 | .fileupload({ 1143 | send: function (e, data) { 1144 | ok( 1145 | !$.blueimpUI.fileupload.prototype.options 1146 | .send.call(this, e, data) 1147 | ); 1148 | return false; 1149 | } 1150 | }) 1151 | .fileupload('send', {files: [{ 1152 | name: 'test', 1153 | size: 1001 1154 | }]}); 1155 | }); 1156 | 1157 | test('minFileSize', function () { 1158 | expect(3); 1159 | var addIndex = 0, 1160 | sendIndex = 0; 1161 | $('#fileupload') 1162 | .fileupload({ 1163 | autoUpload: true, 1164 | minFileSize: 1000, 1165 | send: function (e, data) { 1166 | strictEqual( 1167 | sendIndex += 1, 1168 | addIndex 1169 | ); 1170 | return false; 1171 | } 1172 | }) 1173 | .fileupload('add', {files: [{ 1174 | name: (addIndex += 1) 1175 | }]}) 1176 | .fileupload('add', {files: [{ 1177 | name: (addIndex += 1), 1178 | size: 1001 1179 | }]}) 1180 | .fileupload('add', {files: [{ 1181 | name: 'test', 1182 | size: 999 1183 | }]}) 1184 | .fileupload({ 1185 | send: function (e, data) { 1186 | ok( 1187 | !$.blueimpUI.fileupload.prototype.options 1188 | .send.call(this, e, data) 1189 | ); 1190 | return false; 1191 | } 1192 | }) 1193 | .fileupload('send', {files: [{ 1194 | name: 'test', 1195 | size: 999 1196 | }]}); 1197 | }); 1198 | 1199 | test('acceptFileTypes', function () { 1200 | expect(3); 1201 | var addIndex = 0, 1202 | sendIndex = 0; 1203 | $('#fileupload') 1204 | .fileupload({ 1205 | autoUpload: true, 1206 | acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, 1207 | previewFileTypes: /none/, 1208 | send: function (e, data) { 1209 | strictEqual( 1210 | sendIndex += 1, 1211 | addIndex 1212 | ); 1213 | return false; 1214 | } 1215 | }) 1216 | .fileupload('add', {files: [{ 1217 | name: (addIndex += 1) + '.jpg' 1218 | }]}) 1219 | .fileupload('add', {files: [{ 1220 | name: (addIndex += 1), 1221 | type: 'image/jpeg' 1222 | }]}) 1223 | .fileupload('add', {files: [{ 1224 | name: 'test.txt', 1225 | type: 'text/plain' 1226 | }]}) 1227 | .fileupload({ 1228 | send: function (e, data) { 1229 | ok( 1230 | !$.blueimpUI.fileupload.prototype.options 1231 | .send.call(this, e, data) 1232 | ); 1233 | return false; 1234 | } 1235 | }) 1236 | .fileupload('send', {files: [{ 1237 | name: 'test.txt', 1238 | type: 'text/plain' 1239 | }]}); 1240 | }); 1241 | 1242 | test('acceptFileTypes as HTML5 data attribute', function () { 1243 | expect(2); 1244 | var regExp = /(\.|\/)(gif|jpe?g|png)$/i; 1245 | $('#fileupload') 1246 | .attr('data-accept-file-types', regExp.toString()) 1247 | .fileupload(); 1248 | strictEqual( 1249 | $.type($('#fileupload').fileupload('option', 'acceptFileTypes')), 1250 | $.type(regExp) 1251 | ); 1252 | strictEqual( 1253 | $('#fileupload').fileupload('option', 'acceptFileTypes').toString(), 1254 | regExp.toString() 1255 | ); 1256 | }); 1257 | 1258 | }); 1259 | -------------------------------------------------------------------------------- /public/js/vendor/jquery.ui.widget.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery UI Widget 1.8.23+amd 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 6 | * Dual licensed under the MIT or GPL Version 2 licenses. 7 | * http://jquery.org/license 8 | * 9 | * http://docs.jquery.com/UI/Widget 10 | */ 11 | 12 | (function (factory) { 13 | if (typeof define === "function" && define.amd) { 14 | // Register as an anonymous AMD module: 15 | define(["jquery"], factory); 16 | } else { 17 | // Browser globals: 18 | factory(jQuery); 19 | } 20 | }(function( $, undefined ) { 21 | 22 | // jQuery 1.4+ 23 | if ( $.cleanData ) { 24 | var _cleanData = $.cleanData; 25 | $.cleanData = function( elems ) { 26 | for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { 27 | try { 28 | $( elem ).triggerHandler( "remove" ); 29 | // http://bugs.jquery.com/ticket/8235 30 | } catch( e ) {} 31 | } 32 | _cleanData( elems ); 33 | }; 34 | } else { 35 | var _remove = $.fn.remove; 36 | $.fn.remove = function( selector, keepData ) { 37 | return this.each(function() { 38 | if ( !keepData ) { 39 | if ( !selector || $.filter( selector, [ this ] ).length ) { 40 | $( "*", this ).add( [ this ] ).each(function() { 41 | try { 42 | $( this ).triggerHandler( "remove" ); 43 | // http://bugs.jquery.com/ticket/8235 44 | } catch( e ) {} 45 | }); 46 | } 47 | } 48 | return _remove.call( $(this), selector, keepData ); 49 | }); 50 | }; 51 | } 52 | 53 | $.widget = function( name, base, prototype ) { 54 | var namespace = name.split( "." )[ 0 ], 55 | fullName; 56 | name = name.split( "." )[ 1 ]; 57 | fullName = namespace + "-" + name; 58 | 59 | if ( !prototype ) { 60 | prototype = base; 61 | base = $.Widget; 62 | } 63 | 64 | // create selector for plugin 65 | $.expr[ ":" ][ fullName ] = function( elem ) { 66 | return !!$.data( elem, name ); 67 | }; 68 | 69 | $[ namespace ] = $[ namespace ] || {}; 70 | $[ namespace ][ name ] = function( options, element ) { 71 | // allow instantiation without initializing for simple inheritance 72 | if ( arguments.length ) { 73 | this._createWidget( options, element ); 74 | } 75 | }; 76 | 77 | var basePrototype = new base(); 78 | // we need to make the options hash a property directly on the new instance 79 | // otherwise we'll modify the options hash on the prototype that we're 80 | // inheriting from 81 | // $.each( basePrototype, function( key, val ) { 82 | // if ( $.isPlainObject(val) ) { 83 | // basePrototype[ key ] = $.extend( {}, val ); 84 | // } 85 | // }); 86 | basePrototype.options = $.extend( true, {}, basePrototype.options ); 87 | $[ namespace ][ name ].prototype = $.extend( true, basePrototype, { 88 | namespace: namespace, 89 | widgetName: name, 90 | widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name, 91 | widgetBaseClass: fullName 92 | }, prototype ); 93 | 94 | $.widget.bridge( name, $[ namespace ][ name ] ); 95 | }; 96 | 97 | $.widget.bridge = function( name, object ) { 98 | $.fn[ name ] = function( options ) { 99 | var isMethodCall = typeof options === "string", 100 | args = Array.prototype.slice.call( arguments, 1 ), 101 | returnValue = this; 102 | 103 | // allow multiple hashes to be passed on init 104 | options = !isMethodCall && args.length ? 105 | $.extend.apply( null, [ true, options ].concat(args) ) : 106 | options; 107 | 108 | // prevent calls to internal methods 109 | if ( isMethodCall && options.charAt( 0 ) === "_" ) { 110 | return returnValue; 111 | } 112 | 113 | if ( isMethodCall ) { 114 | this.each(function() { 115 | var instance = $.data( this, name ), 116 | methodValue = instance && $.isFunction( instance[options] ) ? 117 | instance[ options ].apply( instance, args ) : 118 | instance; 119 | // TODO: add this back in 1.9 and use $.error() (see #5972) 120 | // if ( !instance ) { 121 | // throw "cannot call methods on " + name + " prior to initialization; " + 122 | // "attempted to call method '" + options + "'"; 123 | // } 124 | // if ( !$.isFunction( instance[options] ) ) { 125 | // throw "no such method '" + options + "' for " + name + " widget instance"; 126 | // } 127 | // var methodValue = instance[ options ].apply( instance, args ); 128 | if ( methodValue !== instance && methodValue !== undefined ) { 129 | returnValue = methodValue; 130 | return false; 131 | } 132 | }); 133 | } else { 134 | this.each(function() { 135 | var instance = $.data( this, name ); 136 | if ( instance ) { 137 | instance.option( options || {} )._init(); 138 | } else { 139 | $.data( this, name, new object( options, this ) ); 140 | } 141 | }); 142 | } 143 | 144 | return returnValue; 145 | }; 146 | }; 147 | 148 | $.Widget = function( options, element ) { 149 | // allow instantiation without initializing for simple inheritance 150 | if ( arguments.length ) { 151 | this._createWidget( options, element ); 152 | } 153 | }; 154 | 155 | $.Widget.prototype = { 156 | widgetName: "widget", 157 | widgetEventPrefix: "", 158 | options: { 159 | disabled: false 160 | }, 161 | _createWidget: function( options, element ) { 162 | // $.widget.bridge stores the plugin instance, but we do it anyway 163 | // so that it's stored even before the _create function runs 164 | $.data( element, this.widgetName, this ); 165 | this.element = $( element ); 166 | this.options = $.extend( true, {}, 167 | this.options, 168 | this._getCreateOptions(), 169 | options ); 170 | 171 | var self = this; 172 | this.element.bind( "remove." + this.widgetName, function() { 173 | self.destroy(); 174 | }); 175 | 176 | this._create(); 177 | this._trigger( "create" ); 178 | this._init(); 179 | }, 180 | _getCreateOptions: function() { 181 | return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ]; 182 | }, 183 | _create: function() {}, 184 | _init: function() {}, 185 | 186 | destroy: function() { 187 | this.element 188 | .unbind( "." + this.widgetName ) 189 | .removeData( this.widgetName ); 190 | this.widget() 191 | .unbind( "." + this.widgetName ) 192 | .removeAttr( "aria-disabled" ) 193 | .removeClass( 194 | this.widgetBaseClass + "-disabled " + 195 | "ui-state-disabled" ); 196 | }, 197 | 198 | widget: function() { 199 | return this.element; 200 | }, 201 | 202 | option: function( key, value ) { 203 | var options = key; 204 | 205 | if ( arguments.length === 0 ) { 206 | // don't return a reference to the internal hash 207 | return $.extend( {}, this.options ); 208 | } 209 | 210 | if (typeof key === "string" ) { 211 | if ( value === undefined ) { 212 | return this.options[ key ]; 213 | } 214 | options = {}; 215 | options[ key ] = value; 216 | } 217 | 218 | this._setOptions( options ); 219 | 220 | return this; 221 | }, 222 | _setOptions: function( options ) { 223 | var self = this; 224 | $.each( options, function( key, value ) { 225 | self._setOption( key, value ); 226 | }); 227 | 228 | return this; 229 | }, 230 | _setOption: function( key, value ) { 231 | this.options[ key ] = value; 232 | 233 | if ( key === "disabled" ) { 234 | this.widget() 235 | [ value ? "addClass" : "removeClass"]( 236 | this.widgetBaseClass + "-disabled" + " " + 237 | "ui-state-disabled" ) 238 | .attr( "aria-disabled", value ); 239 | } 240 | 241 | return this; 242 | }, 243 | 244 | enable: function() { 245 | return this._setOption( "disabled", false ); 246 | }, 247 | disable: function() { 248 | return this._setOption( "disabled", true ); 249 | }, 250 | 251 | _trigger: function( type, event, data ) { 252 | var prop, orig, 253 | callback = this.options[ type ]; 254 | 255 | data = data || {}; 256 | event = $.Event( event ); 257 | event.type = ( type === this.widgetEventPrefix ? 258 | type : 259 | this.widgetEventPrefix + type ).toLowerCase(); 260 | // the original event may come from any element 261 | // so we need to reset the target on the new event 262 | event.target = this.element[ 0 ]; 263 | 264 | // copy original event properties over to the new event 265 | orig = event.originalEvent; 266 | if ( orig ) { 267 | for ( prop in orig ) { 268 | if ( !( prop in event ) ) { 269 | event[ prop ] = orig[ prop ]; 270 | } 271 | } 272 | } 273 | 274 | this.element.trigger( event, data ); 275 | 276 | return !( $.isFunction(callback) && 277 | callback.call( this.element[0], event, data ) === false || 278 | event.isDefaultPrevented() ); 279 | } 280 | }; 281 | 282 | })); 283 | -------------------------------------------------------------------------------- /public/uploads/files/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zimt28/laravel-jquery-file-upload/be8a238c37c9675664a73e5b75d6d512d23fde3b/public/uploads/files/.gitkeep -------------------------------------------------------------------------------- /public/uploads/thumbnails/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zimt28/laravel-jquery-file-upload/be8a238c37c9675664a73e5b75d6d512d23fde3b/public/uploads/thumbnails/.gitkeep -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # jQuery File Upload Plugin 2 | 3 | ## Demo 4 | [Demo File Upload](http://blueimp.github.com/jQuery-File-Upload/) 5 | 6 | ## Setup 7 | * [How to setup the plugin on your website](https://github.com/blueimp/jQuery-File-Upload/wiki/Setup) 8 | * [How to use only the basic plugin (minimal setup guide).](https://github.com/blueimp/jQuery-File-Upload/wiki/Basic-plugin) 9 | 10 | ## Support 11 | * **Support requests** and **general discussions** about the File Upload plugin can be posted to the official [support forum](https://groups.google.com/d/forum/jquery-fileupload). 12 | If your question is not directly related to the File Upload plugin, you might have a better chance to get a reply by posting to [Stack Overflow](http://stackoverflow.com/questions/tagged/blueimp+jquery+file-upload). 13 | * **Bugs** in the File Upload plugin can be reported using the [issues tracker](https://github.com/blueimp/jQuery-File-Upload/issues). 14 | Please try to reproduce the problem with the [Demo](http://blueimp.github.com/jQuery-File-Upload/) or with an unmodified setup. Problems with customizations should be posted to the [support forum](https://groups.google.com/d/forum/jquery-fileupload), especially for server-side problems. 15 | Provide as much details about your test setup as possible (server information, browser and operating system versions). 16 | Please also provide a [JSFiddle](http://jsfiddle.net/) to allow to reproduce the problem, if possible. 17 | * **Feature requests** can also be posted to the [issues tracker](https://github.com/blueimp/jQuery-File-Upload/issues) if the implementation would benefit a broader use case or the plugin could be considered incomplete without that feature. Else, please post your ideas to the [support forum](https://groups.google.com/d/forum/jquery-fileupload). 18 | 19 | ## Features 20 | * **Multiple file upload:** 21 | Allows to select multiple files at once and upload them simultaneously. 22 | * **Drag & Drop support:** 23 | Allows to upload files by dragging them from your desktop or filemanager and dropping them on your browser window. 24 | * **Upload progress bar:** 25 | Shows a progress bar indicating the upload progress for individual files and for all uploads combined. 26 | * **Cancelable uploads:** 27 | Individual file uploads can be canceled to stop the upload progress. 28 | * **Resumable uploads:** 29 | Aborted uploads can be resumed with browsers supporting the Blob API. 30 | * **Chunked uploads:** 31 | Large files can be uploaded in smaller chunks with browsers supporting the Blob API. 32 | * **Client-side image resizing:** 33 | Images can be automatically resized on client-side with browsers supporting the required JS APIs. 34 | * **Preview images:** 35 | A preview of image files can be displayed before uploading with browsers supporting the required JS APIs. 36 | * **No browser plugins (e.g. Adobe Flash) required:** 37 | The implementation is based on open standards like HTML5 and JavaScript and requires no additional browser plugins. 38 | * **Graceful fallback for legacy browsers:** 39 | Uploads files via XMLHttpRequests if supported and uses iframes as fallback for legacy browsers. 40 | * **HTML file upload form fallback:** 41 | Allows progressive enhancement by using a standard HTML file upload form as widget element. 42 | * **Cross-site file uploads:** 43 | Supports uploading files to a different domain with cross-site XMLHttpRequests or iframe redirects. 44 | * **Multiple plugin instances:** 45 | Allows to use multiple plugin instances on the same webpage. 46 | * **Customizable and extensible:** 47 | Provides an API to set individual options and define callBack methods for various upload events. 48 | * **Multipart and file contents stream uploads:** 49 | Files can be uploaded as standard "multipart/form-data" or file contents stream (HTTP PUT file upload). 50 | * **Compatible with any server-side application platform:** 51 | Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads. 52 | 53 | ## Requirements 54 | * [jQuery](http://jquery.com/) v. 1.6+ 55 | * [jQuery UI widget factory](http://wiki.jqueryui.com/w/page/12138135/Widget%20factory) v. 1.8+ 56 | * [jQuery Iframe Transport plugin](https://github.com/blueimp/jQuery-File-Upload/blob/master/jquery.iframe-transport.js) (included) 57 | * [JavaScript Templates engine](https://github.com/blueimp/JavaScript-Templates) v. 2.1.0+ (optional) 58 | * [JavaScript Load Image function](https://github.com/blueimp/JavaScript-Load-Image) v. 1.1.6+ (optional) 59 | * [JavaScript Canvas to Blob function](https://github.com/blueimp/JavaScript-Canvas-to-Blob) v. 2.0.0+ (optional) 60 | * [Bootstrap CSS Toolkit](https://github.com/twitter/bootstrap/) v. 2.0+ (optional) 61 | 62 | The jQuery UI widget factory is a requirement for the basic File Upload plugin, but very lightweight without any other dependencies. 63 | The jQuery Iframe Transport is required for [browsers without XHR file upload support](https://github.com/blueimp/jQuery-File-Upload/wiki/Browser-support). 64 | The UI version of the File Upload plugin also requires the JavaScript Templates engine as well as the JavaScript Load Image and JavaScript Canvas to Blob functions (for the image previews and resizing functionality). These dependencies are marked as optional, as the basic File Upload plugin can be used without them and the UI version of the plugin can be extended to override these dependencies with alternative solutions. 65 | 66 | The User Interface is built with Twitter's [Bootstrap](https://github.com/twitter/bootstrap/) Toolkit. This enables a CSS based, responsive layout and fancy transition effects on modern browsers. The demo also includes the [Bootstrap Image Gallery Plugin](https://github.com/blueimp/Bootstrap-Image-Gallery). Both of these components are optional and not required. 67 | 68 | The repository also includes the [jQuery XDomainRequest Transport plugin](https://github.com/blueimp/jQuery-File-Upload/blob/master/js/cors/jquery.xdr-transport.js), which enables Cross-domain AJAX requests (GET and POST only) in Microsoft Internet Explorer >= 8. However, the XDomainRequest object doesn't support file uploads and the plugin is only used by the [Demo](http://blueimp.github.com/jQuery-File-Upload/) for Cross-domain requests to delete uploaded files from the demo file upload service. 69 | 70 | [Cross-domain File Uploads](https://github.com/blueimp/jQuery-File-Upload/wiki/Cross-domain-uploads) using the [Iframe Transport plugin](https://github.com/blueimp/jQuery-File-Upload/blob/master/js/jquery.iframe-transport.js) require a redirect back to the origin server to retrieve the upload results. The [example implementation](https://github.com/blueimp/jQuery-File-Upload/blob/master/js/main.js) makes use of [result.html](https://github.com/blueimp/jQuery-File-Upload/blob/master/cors/result.html) as a static redirect page for the origin server. 71 | 72 | ## Browsers 73 | The File Upload plugin is regularly tested with the latest browser versions and supports the following minimal versions: 74 | 75 | * Google Chrome - 7.0+ 76 | * Apple Safari - 4.0+ 77 | * Mozilla Firefox - 3.0+ 78 | * Opera - 10.0+ 79 | * Microsoft Internet Explorer 6.0+ 80 | 81 | Drag & Drop is only supported on Google Chrome, Firefox 4.0+, Safari 5.0+ and Opera 12.0+. 82 | Microsoft Internet Explorer has no support for multiple file selection or upload progress. 83 | [Extended browser support information](https://github.com/blueimp/jQuery-File-Upload/wiki/Browser-support). 84 | 85 | ## License 86 | Released under the [MIT license](http://www.opensource.org/licenses/MIT). 87 | -------------------------------------------------------------------------------- /routes.php: -------------------------------------------------------------------------------- 1 | 'upload', function($folder = null) 14 | { 15 | if ($folder !== null) 16 | $folder .= '/'; 17 | 18 | $upload_handler = IoC::resolve('UploadHandler'); 19 | 20 | if ( ! Request::ajax()) 21 | return; 22 | 23 | switch (Request::method()) 24 | { 25 | case 'OPTIONS': 26 | break; 27 | case 'HEAD': 28 | case 'GET': 29 | $upload_handler->get($folder); 30 | break; 31 | case 'POST': 32 | if (Input::get('_method') === 'DELETE') 33 | { 34 | $upload_handler->delete($folder); 35 | } 36 | else 37 | { 38 | $upload_handler->post($folder); 39 | } 40 | break; 41 | default: 42 | header('HTTP/1.1 405 Method Not Allowed'); 43 | } 44 | })); -------------------------------------------------------------------------------- /start.php: -------------------------------------------------------------------------------- 1 | $val) { 6 | $options[$key] = $val; 7 | } 8 | 9 | IoC::singleton('UploadHandler', function() use ($options) 10 | { 11 | return new UploadHandler($options); 12 | }); -------------------------------------------------------------------------------- /views/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |