[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
1 /** 2 * Plupload - multi-runtime File Uploader 3 * v2.1.9 4 * 5 * Copyright 2013, Moxiecode Systems AB 6 * Released under GPL License. 7 * 8 * License: http://www.plupload.com/license 9 * Contributing: http://www.plupload.com/contributing 10 * 11 * Date: 2016-05-15 12 */ 13 /** 14 * Plupload.js 15 * 16 * Copyright 2013, Moxiecode Systems AB 17 * Released under GPL License. 18 * 19 * License: http://www.plupload.com/license 20 * Contributing: http://www.plupload.com/contributing 21 */ 22 23 /** 24 * Modified for WordPress, Silverlight and Flash runtimes support was removed. 25 * See https://core.trac.wordpress.org/ticket/41755. 26 */ 27 28 /*global mOxie:true */ 29 30 ;(function(window, o, undef) { 31 32 var delay = window.setTimeout 33 , fileFilters = {} 34 ; 35 36 // convert plupload features to caps acceptable by mOxie 37 function normalizeCaps(settings) { 38 var features = settings.required_features, caps = {}; 39 40 function resolve(feature, value, strict) { 41 // Feature notation is deprecated, use caps (this thing here is required for backward compatibility) 42 var map = { 43 chunks: 'slice_blob', 44 jpgresize: 'send_binary_string', 45 pngresize: 'send_binary_string', 46 progress: 'report_upload_progress', 47 multi_selection: 'select_multiple', 48 dragdrop: 'drag_and_drop', 49 drop_element: 'drag_and_drop', 50 headers: 'send_custom_headers', 51 urlstream_upload: 'send_binary_string', 52 canSendBinary: 'send_binary', 53 triggerDialog: 'summon_file_dialog' 54 }; 55 56 if (map[feature]) { 57 caps[map[feature]] = value; 58 } else if (!strict) { 59 caps[feature] = value; 60 } 61 } 62 63 if (typeof(features) === 'string') { 64 plupload.each(features.split(/\s*,\s*/), function(feature) { 65 resolve(feature, true); 66 }); 67 } else if (typeof(features) === 'object') { 68 plupload.each(features, function(value, feature) { 69 resolve(feature, value); 70 }); 71 } else if (features === true) { 72 // check settings for required features 73 if (settings.chunk_size > 0) { 74 caps.slice_blob = true; 75 } 76 77 if (settings.resize.enabled || !settings.multipart) { 78 caps.send_binary_string = true; 79 } 80 81 plupload.each(settings, function(value, feature) { 82 resolve(feature, !!value, true); // strict check 83 }); 84 } 85 86 // WP: only html runtimes. 87 settings.runtimes = 'html5,html4'; 88 89 return caps; 90 } 91 92 /** 93 * @module plupload 94 * @static 95 */ 96 var plupload = { 97 /** 98 * Plupload version will be replaced on build. 99 * 100 * @property VERSION 101 * @for Plupload 102 * @static 103 * @final 104 */ 105 VERSION : '2.1.9', 106 107 /** 108 * The state of the queue before it has started and after it has finished 109 * 110 * @property STOPPED 111 * @static 112 * @final 113 */ 114 STOPPED : 1, 115 116 /** 117 * Upload process is running 118 * 119 * @property STARTED 120 * @static 121 * @final 122 */ 123 STARTED : 2, 124 125 /** 126 * File is queued for upload 127 * 128 * @property QUEUED 129 * @static 130 * @final 131 */ 132 QUEUED : 1, 133 134 /** 135 * File is being uploaded 136 * 137 * @property UPLOADING 138 * @static 139 * @final 140 */ 141 UPLOADING : 2, 142 143 /** 144 * File has failed to be uploaded 145 * 146 * @property FAILED 147 * @static 148 * @final 149 */ 150 FAILED : 4, 151 152 /** 153 * File has been uploaded successfully 154 * 155 * @property DONE 156 * @static 157 * @final 158 */ 159 DONE : 5, 160 161 // Error constants used by the Error event 162 163 /** 164 * Generic error for example if an exception is thrown inside Silverlight. 165 * 166 * @property GENERIC_ERROR 167 * @static 168 * @final 169 */ 170 GENERIC_ERROR : -100, 171 172 /** 173 * HTTP transport error. For example if the server produces a HTTP status other than 200. 174 * 175 * @property HTTP_ERROR 176 * @static 177 * @final 178 */ 179 HTTP_ERROR : -200, 180 181 /** 182 * Generic I/O error. For example if it wasn't possible to open the file stream on local machine. 183 * 184 * @property IO_ERROR 185 * @static 186 * @final 187 */ 188 IO_ERROR : -300, 189 190 /** 191 * @property SECURITY_ERROR 192 * @static 193 * @final 194 */ 195 SECURITY_ERROR : -400, 196 197 /** 198 * Initialization error. Will be triggered if no runtime was initialized. 199 * 200 * @property INIT_ERROR 201 * @static 202 * @final 203 */ 204 INIT_ERROR : -500, 205 206 /** 207 * File size error. If the user selects a file that is too large it will be blocked and an error of this type will be triggered. 208 * 209 * @property FILE_SIZE_ERROR 210 * @static 211 * @final 212 */ 213 FILE_SIZE_ERROR : -600, 214 215 /** 216 * File extension error. If the user selects a file that isn't valid according to the filters setting. 217 * 218 * @property FILE_EXTENSION_ERROR 219 * @static 220 * @final 221 */ 222 FILE_EXTENSION_ERROR : -601, 223 224 /** 225 * Duplicate file error. If prevent_duplicates is set to true and user selects the same file again. 226 * 227 * @property FILE_DUPLICATE_ERROR 228 * @static 229 * @final 230 */ 231 FILE_DUPLICATE_ERROR : -602, 232 233 /** 234 * Runtime will try to detect if image is proper one. Otherwise will throw this error. 235 * 236 * @property IMAGE_FORMAT_ERROR 237 * @static 238 * @final 239 */ 240 IMAGE_FORMAT_ERROR : -700, 241 242 /** 243 * While working on files runtime may run out of memory and will throw this error. 244 * 245 * @since 2.1.2 246 * @property MEMORY_ERROR 247 * @static 248 * @final 249 */ 250 MEMORY_ERROR : -701, 251 252 /** 253 * Each runtime has an upper limit on a dimension of the image it can handle. If bigger, will throw this error. 254 * 255 * @property IMAGE_DIMENSIONS_ERROR 256 * @static 257 * @final 258 */ 259 IMAGE_DIMENSIONS_ERROR : -702, 260 261 /** 262 * Mime type lookup table. 263 * 264 * @property mimeTypes 265 * @type Object 266 * @final 267 */ 268 mimeTypes : o.mimes, 269 270 /** 271 * In some cases sniffing is the only way around :( 272 */ 273 ua: o.ua, 274 275 /** 276 * Gets the true type of the built-in object (better version of typeof). 277 * @credits Angus Croll (http://javascriptweblog.wordpress.com/) 278 * 279 * @method typeOf 280 * @static 281 * @param {Object} o Object to check. 282 * @return {String} Object [[Class]] 283 */ 284 typeOf: o.typeOf, 285 286 /** 287 * Extends the specified object with another object. 288 * 289 * @method extend 290 * @static 291 * @param {Object} target Object to extend. 292 * @param {Object..} obj Multiple objects to extend with. 293 * @return {Object} Same as target, the extended object. 294 */ 295 extend : o.extend, 296 297 /** 298 * Generates an unique ID. This is 99.99% unique since it takes the current time and 5 random numbers. 299 * The only way a user would be able to get the same ID is if the two persons at the same exact millisecond manages 300 * to get 5 the same random numbers between 0-65535 it also uses a counter so each call will be guaranteed to be page unique. 301 * It's more probable for the earth to be hit with an asteriod. You can also if you want to be 100% sure set the plupload.guidPrefix property 302 * to an user unique key. 303 * 304 * @method guid 305 * @static 306 * @return {String} Virtually unique id. 307 */ 308 guid : o.guid, 309 310 /** 311 * Get array of DOM Elements by their ids. 312 * 313 * @method get 314 * @param {String} id Identifier of the DOM Element 315 * @return {Array} 316 */ 317 getAll : function get(ids) { 318 var els = [], el; 319 320 if (plupload.typeOf(ids) !== 'array') { 321 ids = [ids]; 322 } 323 324 var i = ids.length; 325 while (i--) { 326 el = plupload.get(ids[i]); 327 if (el) { 328 els.push(el); 329 } 330 } 331 332 return els.length ? els : null; 333 }, 334 335 /** 336 Get DOM element by id 337 338 @method get 339 @param {String} id Identifier of the DOM Element 340 @return {Node} 341 */ 342 get: o.get, 343 344 /** 345 * Executes the callback function for each item in array/object. If you return false in the 346 * callback it will break the loop. 347 * 348 * @method each 349 * @static 350 * @param {Object} obj Object to iterate. 351 * @param {function} callback Callback function to execute for each item. 352 */ 353 each : o.each, 354 355 /** 356 * Returns the absolute x, y position of an Element. The position will be returned in a object with x, y fields. 357 * 358 * @method getPos 359 * @static 360 * @param {Element} node HTML element or element id to get x, y position from. 361 * @param {Element} root Optional root element to stop calculations at. 362 * @return {object} Absolute position of the specified element object with x, y fields. 363 */ 364 getPos : o.getPos, 365 366 /** 367 * Returns the size of the specified node in pixels. 368 * 369 * @method getSize 370 * @static 371 * @param {Node} node Node to get the size of. 372 * @return {Object} Object with a w and h property. 373 */ 374 getSize : o.getSize, 375 376 /** 377 * Encodes the specified string. 378 * 379 * @method xmlEncode 380 * @static 381 * @param {String} s String to encode. 382 * @return {String} Encoded string. 383 */ 384 xmlEncode : function(str) { 385 var xmlEncodeChars = {'<' : 'lt', '>' : 'gt', '&' : 'amp', '"' : 'quot', '\'' : '#39'}, xmlEncodeRegExp = /[<>&\"\']/g; 386 387 return str ? ('' + str).replace(xmlEncodeRegExp, function(chr) { 388 return xmlEncodeChars[chr] ? '&' + xmlEncodeChars[chr] + ';' : chr; 389 }) : str; 390 }, 391 392 /** 393 * Forces anything into an array. 394 * 395 * @method toArray 396 * @static 397 * @param {Object} obj Object with length field. 398 * @return {Array} Array object containing all items. 399 */ 400 toArray : o.toArray, 401 402 /** 403 * Find an element in array and return its index if present, otherwise return -1. 404 * 405 * @method inArray 406 * @static 407 * @param {mixed} needle Element to find 408 * @param {Array} array 409 * @return {Int} Index of the element, or -1 if not found 410 */ 411 inArray : o.inArray, 412 413 /** 414 * Extends the language pack object with new items. 415 * 416 * @method addI18n 417 * @static 418 * @param {Object} pack Language pack items to add. 419 * @return {Object} Extended language pack object. 420 */ 421 addI18n : o.addI18n, 422 423 /** 424 * Translates the specified string by checking for the english string in the language pack lookup. 425 * 426 * @method translate 427 * @static 428 * @param {String} str String to look for. 429 * @return {String} Translated string or the input string if it wasn't found. 430 */ 431 translate : o.translate, 432 433 /** 434 * Checks if object is empty. 435 * 436 * @method isEmptyObj 437 * @static 438 * @param {Object} obj Object to check. 439 * @return {Boolean} 440 */ 441 isEmptyObj : o.isEmptyObj, 442 443 /** 444 * Checks if specified DOM element has specified class. 445 * 446 * @method hasClass 447 * @static 448 * @param {Object} obj DOM element like object to add handler to. 449 * @param {String} name Class name 450 */ 451 hasClass : o.hasClass, 452 453 /** 454 * Adds specified className to specified DOM element. 455 * 456 * @method addClass 457 * @static 458 * @param {Object} obj DOM element like object to add handler to. 459 * @param {String} name Class name 460 */ 461 addClass : o.addClass, 462 463 /** 464 * Removes specified className from specified DOM element. 465 * 466 * @method removeClass 467 * @static 468 * @param {Object} obj DOM element like object to add handler to. 469 * @param {String} name Class name 470 */ 471 removeClass : o.removeClass, 472 473 /** 474 * Returns a given computed style of a DOM element. 475 * 476 * @method getStyle 477 * @static 478 * @param {Object} obj DOM element like object. 479 * @param {String} name Style you want to get from the DOM element 480 */ 481 getStyle : o.getStyle, 482 483 /** 484 * Adds an event handler to the specified object and store reference to the handler 485 * in objects internal Plupload registry (@see removeEvent). 486 * 487 * @method addEvent 488 * @static 489 * @param {Object} obj DOM element like object to add handler to. 490 * @param {String} name Name to add event listener to. 491 * @param {Function} callback Function to call when event occurs. 492 * @param {String} (optional) key that might be used to add specifity to the event record. 493 */ 494 addEvent : o.addEvent, 495 496 /** 497 * Remove event handler from the specified object. If third argument (callback) 498 * is not specified remove all events with the specified name. 499 * 500 * @method removeEvent 501 * @static 502 * @param {Object} obj DOM element to remove event listener(s) from. 503 * @param {String} name Name of event listener to remove. 504 * @param {Function|String} (optional) might be a callback or unique key to match. 505 */ 506 removeEvent: o.removeEvent, 507 508 /** 509 * Remove all kind of events from the specified object 510 * 511 * @method removeAllEvents 512 * @static 513 * @param {Object} obj DOM element to remove event listeners from. 514 * @param {String} (optional) unique key to match, when removing events. 515 */ 516 removeAllEvents: o.removeAllEvents, 517 518 /** 519 * Cleans the specified name from national characters (diacritics). The result will be a name with only a-z, 0-9 and _. 520 * 521 * @method cleanName 522 * @static 523 * @param {String} s String to clean up. 524 * @return {String} Cleaned string. 525 */ 526 cleanName : function(name) { 527 var i, lookup; 528 529 // Replace diacritics 530 lookup = [ 531 /[\300-\306]/g, 'A', /[\340-\346]/g, 'a', 532 /\307/g, 'C', /\347/g, 'c', 533 /[\310-\313]/g, 'E', /[\350-\353]/g, 'e', 534 /[\314-\317]/g, 'I', /[\354-\357]/g, 'i', 535 /\321/g, 'N', /\361/g, 'n', 536 /[\322-\330]/g, 'O', /[\362-\370]/g, 'o', 537 /[\331-\334]/g, 'U', /[\371-\374]/g, 'u' 538 ]; 539 540 for (i = 0; i < lookup.length; i += 2) { 541 name = name.replace(lookup[i], lookup[i + 1]); 542 } 543 544 // Replace whitespace 545 name = name.replace(/\s+/g, '_'); 546 547 // Remove anything else 548 name = name.replace(/[^a-z0-9_\-\.]+/gi, ''); 549 550 return name; 551 }, 552 553 /** 554 * Builds a full url out of a base URL and an object with items to append as query string items. 555 * 556 * @method buildUrl 557 * @static 558 * @param {String} url Base URL to append query string items to. 559 * @param {Object} items Name/value object to serialize as a querystring. 560 * @return {String} String with url + serialized query string items. 561 */ 562 buildUrl : function(url, items) { 563 var query = ''; 564 565 plupload.each(items, function(value, name) { 566 query += (query ? '&' : '') + encodeURIComponent(name) + '=' + encodeURIComponent(value); 567 }); 568 569 if (query) { 570 url += (url.indexOf('?') > 0 ? '&' : '?') + query; 571 } 572 573 return url; 574 }, 575 576 /** 577 * Formats the specified number as a size string for example 1024 becomes 1 KB. 578 * 579 * @method formatSize 580 * @static 581 * @param {Number} size Size to format as string. 582 * @return {String} Formatted size string. 583 */ 584 formatSize : function(size) { 585 586 if (size === undef || /\D/.test(size)) { 587 return plupload.translate('N/A'); 588 } 589 590 function round(num, precision) { 591 return Math.round(num * Math.pow(10, precision)) / Math.pow(10, precision); 592 } 593 594 var boundary = Math.pow(1024, 4); 595 596 // TB 597 if (size > boundary) { 598 return round(size / boundary, 1) + " " + plupload.translate('tb'); 599 } 600 601 // GB 602 if (size > (boundary/=1024)) { 603 return round(size / boundary, 1) + " " + plupload.translate('gb'); 604 } 605 606 // MB 607 if (size > (boundary/=1024)) { 608 return round(size / boundary, 1) + " " + plupload.translate('mb'); 609 } 610 611 // KB 612 if (size > 1024) { 613 return Math.round(size / 1024) + " " + plupload.translate('kb'); 614 } 615 616 return size + " " + plupload.translate('b'); 617 }, 618 619 620 /** 621 * Parses the specified size string into a byte value. For example 10kb becomes 10240. 622 * 623 * @method parseSize 624 * @static 625 * @param {String|Number} size String to parse or number to just pass through. 626 * @return {Number} Size in bytes. 627 */ 628 parseSize : o.parseSizeStr, 629 630 631 /** 632 * A way to predict what runtime will be choosen in the current environment with the 633 * specified settings. 634 * 635 * @method predictRuntime 636 * @static 637 * @param {Object|String} config Plupload settings to check 638 * @param {String} [runtimes] Comma-separated list of runtimes to check against 639 * @return {String} Type of compatible runtime 640 */ 641 predictRuntime : function(config, runtimes) { 642 var up, runtime; 643 644 up = new plupload.Uploader(config); 645 runtime = o.Runtime.thatCan(up.getOption().required_features, runtimes || config.runtimes); 646 up.destroy(); 647 return runtime; 648 }, 649 650 /** 651 * Registers a filter that will be executed for each file added to the queue. 652 * If callback returns false, file will not be added. 653 * 654 * Callback receives two arguments: a value for the filter as it was specified in settings.filters 655 * and a file to be filtered. Callback is executed in the context of uploader instance. 656 * 657 * @method addFileFilter 658 * @static 659 * @param {String} name Name of the filter by which it can be referenced in settings.filters 660 * @param {String} cb Callback - the actual routine that every added file must pass 661 */ 662 addFileFilter: function(name, cb) { 663 fileFilters[name] = cb; 664 } 665 }; 666 667 668 plupload.addFileFilter('mime_types', function(filters, file, cb) { 669 if (filters.length && !filters.regexp.test(file.name)) { 670 this.trigger('Error', { 671 code : plupload.FILE_EXTENSION_ERROR, 672 message : plupload.translate('File extension error.'), 673 file : file 674 }); 675 cb(false); 676 } else { 677 cb(true); 678 } 679 }); 680 681 682 plupload.addFileFilter('max_file_size', function(maxSize, file, cb) { 683 var undef; 684 685 maxSize = plupload.parseSize(maxSize); 686 687 // Invalid file size 688 if (file.size !== undef && maxSize && file.size > maxSize) { 689 this.trigger('Error', { 690 code : plupload.FILE_SIZE_ERROR, 691 message : plupload.translate('File size error.'), 692 file : file 693 }); 694 cb(false); 695 } else { 696 cb(true); 697 } 698 }); 699 700 701 plupload.addFileFilter('prevent_duplicates', function(value, file, cb) { 702 if (value) { 703 var ii = this.files.length; 704 while (ii--) { 705 // Compare by name and size (size might be 0 or undefined, but still equivalent for both) 706 if (file.name === this.files[ii].name && file.size === this.files[ii].size) { 707 this.trigger('Error', { 708 code : plupload.FILE_DUPLICATE_ERROR, 709 message : plupload.translate('Duplicate file error.'), 710 file : file 711 }); 712 cb(false); 713 return; 714 } 715 } 716 } 717 cb(true); 718 }); 719 720 721 /** 722 @class Uploader 723 @constructor 724 725 @param {Object} settings For detailed information about each option check documentation. 726 @param {String|DOMElement} settings.browse_button id of the DOM element or DOM element itself to use as file dialog trigger. 727 @param {String} settings.url URL of the server-side upload handler. 728 @param {Number|String} [settings.chunk_size=0] Chunk size in bytes to slice the file into. Shorcuts with b, kb, mb, gb, tb suffixes also supported. `e.g. 204800 or "204800b" or "200kb"`. By default - disabled. 729 @param {Boolean} [settings.send_chunk_number=true] Whether to send chunks and chunk numbers, or total and offset bytes. 730 @param {String|DOMElement} [settings.container] id of the DOM element or DOM element itself that will be used to wrap uploader structures. Defaults to immediate parent of the `browse_button` element. 731 @param {String|DOMElement} [settings.drop_element] id of the DOM element or DOM element itself to use as a drop zone for Drag-n-Drop. 732 @param {String} [settings.file_data_name="file"] Name for the file field in Multipart formated message. 733 @param {Object} [settings.filters={}] Set of file type filters. 734 @param {Array} [settings.filters.mime_types=[]] List of file types to accept, each one defined by title and list of extensions. `e.g. {title : "Image files", extensions : "jpg,jpeg,gif,png"}`. Dispatches `plupload.FILE_EXTENSION_ERROR` 735 @param {String|Number} [settings.filters.max_file_size=0] Maximum file size that the user can pick, in bytes. Optionally supports b, kb, mb, gb, tb suffixes. `e.g. "10mb" or "1gb"`. By default - not set. Dispatches `plupload.FILE_SIZE_ERROR`. 736 @param {Boolean} [settings.filters.prevent_duplicates=false] Do not let duplicates into the queue. Dispatches `plupload.FILE_DUPLICATE_ERROR`. 737 @param {String} [settings.flash_swf_url] URL of the Flash swf. (Not used in WordPress) 738 @param {Object} [settings.headers] Custom headers to send with the upload. Hash of name/value pairs. 739 @param {Number} [settings.max_retries=0] How many times to retry the chunk or file, before triggering Error event. 740 @param {Boolean} [settings.multipart=true] Whether to send file and additional parameters as Multipart formated message. 741 @param {Object} [settings.multipart_params] Hash of key/value pairs to send with every file upload. 742 @param {Boolean} [settings.multi_selection=true] Enable ability to select multiple files at once in file dialog. 743 @param {String|Object} [settings.required_features] Either comma-separated list or hash of required features that chosen runtime should absolutely possess. 744 @param {Object} [settings.resize] Enable resizng of images on client-side. Applies to `image/jpeg` and `image/png` only. `e.g. {width : 200, height : 200, quality : 90, crop: true}` 745 @param {Number} [settings.resize.width] If image is bigger, it will be resized. 746 @param {Number} [settings.resize.height] If image is bigger, it will be resized. 747 @param {Number} [settings.resize.quality=90] Compression quality for jpegs (1-100). 748 @param {Boolean} [settings.resize.crop=false] Whether to crop images to exact dimensions. By default they will be resized proportionally. 749 @param {String} [settings.runtimes="html5,html4"] Comma separated list of runtimes, that Plupload will try in turn, moving to the next if previous fails. 750 @param {String} [settings.silverlight_xap_url] URL of the Silverlight xap. (Not used in WordPress) 751 @param {Boolean} [settings.unique_names=false] If true will generate unique filenames for uploaded files. 752 @param {Boolean} [settings.send_file_name=true] Whether to send file name as additional argument - 'name' (required for chunked uploads and some other cases where file name cannot be sent via normal ways). 753 */ 754 plupload.Uploader = function(options) { 755 /** 756 Fires when the current RunTime has been initialized. 757 758 @event Init 759 @param {plupload.Uploader} uploader Uploader instance sending the event. 760 */ 761 762 /** 763 Fires after the init event incase you need to perform actions there. 764 765 @event PostInit 766 @param {plupload.Uploader} uploader Uploader instance sending the event. 767 */ 768 769 /** 770 Fires when the option is changed in via uploader.setOption(). 771 772 @event OptionChanged 773 @since 2.1 774 @param {plupload.Uploader} uploader Uploader instance sending the event. 775 @param {String} name Name of the option that was changed 776 @param {Mixed} value New value for the specified option 777 @param {Mixed} oldValue Previous value of the option 778 */ 779 780 /** 781 Fires when the silverlight/flash or other shim needs to move. 782 783 @event Refresh 784 @param {plupload.Uploader} uploader Uploader instance sending the event. 785 */ 786 787 /** 788 Fires when the overall state is being changed for the upload queue. 789 790 @event StateChanged 791 @param {plupload.Uploader} uploader Uploader instance sending the event. 792 */ 793 794 /** 795 Fires when browse_button is clicked and browse dialog shows. 796 797 @event Browse 798 @since 2.1.2 799 @param {plupload.Uploader} uploader Uploader instance sending the event. 800 */ 801 802 /** 803 Fires for every filtered file before it is added to the queue. 804 805 @event FileFiltered 806 @since 2.1 807 @param {plupload.Uploader} uploader Uploader instance sending the event. 808 @param {plupload.File} file Another file that has to be added to the queue. 809 */ 810 811 /** 812 Fires when the file queue is changed. In other words when files are added/removed to the files array of the uploader instance. 813 814 @event QueueChanged 815 @param {plupload.Uploader} uploader Uploader instance sending the event. 816 */ 817 818 /** 819 Fires after files were filtered and added to the queue. 820 821 @event FilesAdded 822 @param {plupload.Uploader} uploader Uploader instance sending the event. 823 @param {Array} files Array of file objects that were added to queue by the user. 824 */ 825 826 /** 827 Fires when file is removed from the queue. 828 829 @event FilesRemoved 830 @param {plupload.Uploader} uploader Uploader instance sending the event. 831 @param {Array} files Array of files that got removed. 832 */ 833 834 /** 835 Fires just before a file is uploaded. Can be used to cancel the upload for the specified file 836 by returning false from the handler. 837 838 @event BeforeUpload 839 @param {plupload.Uploader} uploader Uploader instance sending the event. 840 @param {plupload.File} file File to be uploaded. 841 */ 842 843 /** 844 Fires when a file is to be uploaded by the runtime. 845 846 @event UploadFile 847 @param {plupload.Uploader} uploader Uploader instance sending the event. 848 @param {plupload.File} file File to be uploaded. 849 */ 850 851 /** 852 Fires while a file is being uploaded. Use this event to update the current file upload progress. 853 854 @event UploadProgress 855 @param {plupload.Uploader} uploader Uploader instance sending the event. 856 @param {plupload.File} file File that is currently being uploaded. 857 */ 858 859 /** 860 Fires when file chunk is uploaded. 861 862 @event ChunkUploaded 863 @param {plupload.Uploader} uploader Uploader instance sending the event. 864 @param {plupload.File} file File that the chunk was uploaded for. 865 @param {Object} result Object with response properties. 866 @param {Number} result.offset The amount of bytes the server has received so far, including this chunk. 867 @param {Number} result.total The size of the file. 868 @param {String} result.response The response body sent by the server. 869 @param {Number} result.status The HTTP status code sent by the server. 870 @param {String} result.responseHeaders All the response headers as a single string. 871 */ 872 873 /** 874 Fires when a file is successfully uploaded. 875 876 @event FileUploaded 877 @param {plupload.Uploader} uploader Uploader instance sending the event. 878 @param {plupload.File} file File that was uploaded. 879 @param {Object} result Object with response properties. 880 @param {String} result.response The response body sent by the server. 881 @param {Number} result.status The HTTP status code sent by the server. 882 @param {String} result.responseHeaders All the response headers as a single string. 883 */ 884 885 /** 886 Fires when all files in a queue are uploaded. 887 888 @event UploadComplete 889 @param {plupload.Uploader} uploader Uploader instance sending the event. 890 @param {Array} files Array of file objects that was added to queue/selected by the user. 891 */ 892 893 /** 894 Fires when a error occurs. 895 896 @event Error 897 @param {plupload.Uploader} uploader Uploader instance sending the event. 898 @param {Object} error Contains code, message and sometimes file and other details. 899 @param {Number} error.code The plupload error code. 900 @param {String} error.message Description of the error (uses i18n). 901 */ 902 903 /** 904 Fires when destroy method is called. 905 906 @event Destroy 907 @param {plupload.Uploader} uploader Uploader instance sending the event. 908 */ 909 var uid = plupload.guid() 910 , settings 911 , files = [] 912 , preferred_caps = {} 913 , fileInputs = [] 914 , fileDrops = [] 915 , startTime 916 , total 917 , disabled = false 918 , xhr 919 ; 920 921 922 // Private methods 923 function uploadNext() { 924 var file, count = 0, i; 925 926 if (this.state == plupload.STARTED) { 927 // Find first QUEUED file 928 for (i = 0; i < files.length; i++) { 929 if (!file && files[i].status == plupload.QUEUED) { 930 file = files[i]; 931 if (this.trigger("BeforeUpload", file)) { 932 file.status = plupload.UPLOADING; 933 this.trigger("UploadFile", file); 934 } 935 } else { 936 count++; 937 } 938 } 939 940 // All files are DONE or FAILED 941 if (count == files.length) { 942 if (this.state !== plupload.STOPPED) { 943 this.state = plupload.STOPPED; 944 this.trigger("StateChanged"); 945 } 946 this.trigger("UploadComplete", files); 947 } 948 } 949 } 950 951 952 function calcFile(file) { 953 file.percent = file.size > 0 ? Math.ceil(file.loaded / file.size * 100) : 100; 954 calc(); 955 } 956 957 958 function calc() { 959 var i, file; 960 961 // Reset stats 962 total.reset(); 963 964 // Check status, size, loaded etc on all files 965 for (i = 0; i < files.length; i++) { 966 file = files[i]; 967 968 if (file.size !== undef) { 969 // We calculate totals based on original file size 970 total.size += file.origSize; 971 972 // Since we cannot predict file size after resize, we do opposite and 973 // interpolate loaded amount to match magnitude of total 974 total.loaded += file.loaded * file.origSize / file.size; 975 } else { 976 total.size = undef; 977 } 978 979 if (file.status == plupload.DONE) { 980 total.uploaded++; 981 } else if (file.status == plupload.FAILED) { 982 total.failed++; 983 } else { 984 total.queued++; 985 } 986 } 987 988 // If we couldn't calculate a total file size then use the number of files to calc percent 989 if (total.size === undef) { 990 total.percent = files.length > 0 ? Math.ceil(total.uploaded / files.length * 100) : 0; 991 } else { 992 total.bytesPerSec = Math.ceil(total.loaded / ((+new Date() - startTime || 1) / 1000.0)); 993 total.percent = total.size > 0 ? Math.ceil(total.loaded / total.size * 100) : 0; 994 } 995 } 996 997 998 function getRUID() { 999 var ctrl = fileInputs[0] || fileDrops[0]; 1000 if (ctrl) { 1001 return ctrl.getRuntime().uid; 1002 } 1003 return false; 1004 } 1005 1006 1007 function runtimeCan(file, cap) { 1008 if (file.ruid) { 1009 var info = o.Runtime.getInfo(file.ruid); 1010 if (info) { 1011 return info.can(cap); 1012 } 1013 } 1014 return false; 1015 } 1016 1017 1018 function bindEventListeners() { 1019 this.bind('FilesAdded FilesRemoved', function(up) { 1020 up.trigger('QueueChanged'); 1021 up.refresh(); 1022 }); 1023 1024 this.bind('CancelUpload', onCancelUpload); 1025 1026 this.bind('BeforeUpload', onBeforeUpload); 1027 1028 this.bind('UploadFile', onUploadFile); 1029 1030 this.bind('UploadProgress', onUploadProgress); 1031 1032 this.bind('StateChanged', onStateChanged); 1033 1034 this.bind('QueueChanged', calc); 1035 1036 this.bind('Error', onError); 1037 1038 this.bind('FileUploaded', onFileUploaded); 1039 1040 this.bind('Destroy', onDestroy); 1041 } 1042 1043 1044 function initControls(settings, cb) { 1045 var self = this, inited = 0, queue = []; 1046 1047 // common settings 1048 var options = { 1049 runtime_order: settings.runtimes, 1050 required_caps: settings.required_features, 1051 preferred_caps: preferred_caps 1052 }; 1053 1054 // add runtime specific options if any 1055 plupload.each(settings.runtimes.split(/\s*,\s*/), function(runtime) { 1056 if (settings[runtime]) { 1057 options[runtime] = settings[runtime]; 1058 } 1059 }); 1060 1061 // initialize file pickers - there can be many 1062 if (settings.browse_button) { 1063 plupload.each(settings.browse_button, function(el) { 1064 queue.push(function(cb) { 1065 var fileInput = new o.FileInput(plupload.extend({}, options, { 1066 accept: settings.filters.mime_types, 1067 name: settings.file_data_name, 1068 multiple: settings.multi_selection, 1069 container: settings.container, 1070 browse_button: el 1071 })); 1072 1073 fileInput.onready = function() { 1074 var info = o.Runtime.getInfo(this.ruid); 1075 1076 // for backward compatibility 1077 o.extend(self.features, { 1078 chunks: info.can('slice_blob'), 1079 multipart: info.can('send_multipart'), 1080 multi_selection: info.can('select_multiple') 1081 }); 1082 1083 inited++; 1084 fileInputs.push(this); 1085 cb(); 1086 }; 1087 1088 fileInput.onchange = function() { 1089 self.addFile(this.files); 1090 }; 1091 1092 fileInput.bind('mouseenter mouseleave mousedown mouseup', function(e) { 1093 if (!disabled) { 1094 if (settings.browse_button_hover) { 1095 if ('mouseenter' === e.type) { 1096 o.addClass(el, settings.browse_button_hover); 1097 } else if ('mouseleave' === e.type) { 1098 o.removeClass(el, settings.browse_button_hover); 1099 } 1100 } 1101 1102 if (settings.browse_button_active) { 1103 if ('mousedown' === e.type) { 1104 o.addClass(el, settings.browse_button_active); 1105 } else if ('mouseup' === e.type) { 1106 o.removeClass(el, settings.browse_button_active); 1107 } 1108 } 1109 } 1110 }); 1111 1112 fileInput.bind('mousedown', function() { 1113 self.trigger('Browse'); 1114 }); 1115 1116 fileInput.bind('error runtimeerror', function() { 1117 fileInput = null; 1118 cb(); 1119 }); 1120 1121 fileInput.init(); 1122 }); 1123 }); 1124 } 1125 1126 // initialize drop zones 1127 if (settings.drop_element) { 1128 plupload.each(settings.drop_element, function(el) { 1129 queue.push(function(cb) { 1130 var fileDrop = new o.FileDrop(plupload.extend({}, options, { 1131 drop_zone: el 1132 })); 1133 1134 fileDrop.onready = function() { 1135 var info = o.Runtime.getInfo(this.ruid); 1136 1137 // for backward compatibility 1138 o.extend(self.features, { 1139 chunks: info.can('slice_blob'), 1140 multipart: info.can('send_multipart'), 1141 dragdrop: info.can('drag_and_drop') 1142 }); 1143 1144 inited++; 1145 fileDrops.push(this); 1146 cb(); 1147 }; 1148 1149 fileDrop.ondrop = function() { 1150 self.addFile(this.files); 1151 }; 1152 1153 fileDrop.bind('error runtimeerror', function() { 1154 fileDrop = null; 1155 cb(); 1156 }); 1157 1158 fileDrop.init(); 1159 }); 1160 }); 1161 } 1162 1163 1164 o.inSeries(queue, function() { 1165 if (typeof(cb) === 'function') { 1166 cb(inited); 1167 } 1168 }); 1169 } 1170 1171 1172 function resizeImage(blob, params, cb) { 1173 var img = new o.Image(); 1174 1175 try { 1176 img.onload = function() { 1177 // no manipulation required if... 1178 if (params.width > this.width && 1179 params.height > this.height && 1180 params.quality === undef && 1181 params.preserve_headers && 1182 !params.crop 1183 ) { 1184 this.destroy(); 1185 return cb(blob); 1186 } 1187 // otherwise downsize 1188 img.downsize(params.width, params.height, params.crop, params.preserve_headers); 1189 }; 1190 1191 img.onresize = function() { 1192 cb(this.getAsBlob(blob.type, params.quality)); 1193 this.destroy(); 1194 }; 1195 1196 img.onerror = function() { 1197 cb(blob); 1198 }; 1199 1200 img.load(blob); 1201 } catch(ex) { 1202 cb(blob); 1203 } 1204 } 1205 1206 1207 function setOption(option, value, init) { 1208 var self = this, reinitRequired = false; 1209 1210 function _setOption(option, value, init) { 1211 var oldValue = settings[option]; 1212 1213 switch (option) { 1214 case 'max_file_size': 1215 if (option === 'max_file_size') { 1216 settings.max_file_size = settings.filters.max_file_size = value; 1217 } 1218 break; 1219 1220 case 'chunk_size': 1221 if (value = plupload.parseSize(value)) { 1222 settings[option] = value; 1223 settings.send_file_name = true; 1224 } 1225 break; 1226 1227 case 'multipart': 1228 settings[option] = value; 1229 if (!value) { 1230 settings.send_file_name = true; 1231 } 1232 break; 1233 1234 case 'unique_names': 1235 settings[option] = value; 1236 if (value) { 1237 settings.send_file_name = true; 1238 } 1239 break; 1240 1241 case 'filters': 1242 // for sake of backward compatibility 1243 if (plupload.typeOf(value) === 'array') { 1244 value = { 1245 mime_types: value 1246 }; 1247 } 1248 1249 if (init) { 1250 plupload.extend(settings.filters, value); 1251 } else { 1252 settings.filters = value; 1253 } 1254 1255 // if file format filters are being updated, regenerate the matching expressions 1256 if (value.mime_types) { 1257 settings.filters.mime_types.regexp = (function(filters) { 1258 var extensionsRegExp = []; 1259 1260 plupload.each(filters, function(filter) { 1261 plupload.each(filter.extensions.split(/,/), function(ext) { 1262 if (/^\s*\*\s*$/.test(ext)) { 1263 extensionsRegExp.push('\\.*'); 1264 } else { 1265 extensionsRegExp.push('\\.' + ext.replace(new RegExp('[' + ('/^$.*+?|()[]{}\\'.replace(/./g, '\\$&')) + ']', 'g'), '\\$&')); 1266 } 1267 }); 1268 }); 1269 1270 return new RegExp('(' + extensionsRegExp.join('|') + ')$', 'i'); 1271 }(settings.filters.mime_types)); 1272 } 1273 break; 1274 1275 case 'resize': 1276 if (init) { 1277 plupload.extend(settings.resize, value, { 1278 enabled: true 1279 }); 1280 } else { 1281 settings.resize = value; 1282 } 1283 break; 1284 1285 case 'prevent_duplicates': 1286 settings.prevent_duplicates = settings.filters.prevent_duplicates = !!value; 1287 break; 1288 1289 // options that require reinitialisation 1290 case 'container': 1291 case 'browse_button': 1292 case 'drop_element': 1293 value = 'container' === option 1294 ? plupload.get(value) 1295 : plupload.getAll(value) 1296 ; 1297 1298 case 'runtimes': 1299 case 'multi_selection': 1300 settings[option] = value; 1301 if (!init) { 1302 reinitRequired = true; 1303 } 1304 break; 1305 1306 default: 1307 settings[option] = value; 1308 } 1309 1310 if (!init) { 1311 self.trigger('OptionChanged', option, value, oldValue); 1312 } 1313 } 1314 1315 if (typeof(option) === 'object') { 1316 plupload.each(option, function(value, option) { 1317 _setOption(option, value, init); 1318 }); 1319 } else { 1320 _setOption(option, value, init); 1321 } 1322 1323 if (init) { 1324 // Normalize the list of required capabilities 1325 settings.required_features = normalizeCaps(plupload.extend({}, settings)); 1326 1327 // Come up with the list of capabilities that can affect default mode in a multi-mode runtimes 1328 preferred_caps = normalizeCaps(plupload.extend({}, settings, { 1329 required_features: true 1330 })); 1331 } else if (reinitRequired) { 1332 self.trigger('Destroy'); 1333 1334 initControls.call(self, settings, function(inited) { 1335 if (inited) { 1336 self.runtime = o.Runtime.getInfo(getRUID()).type; 1337 self.trigger('Init', { runtime: self.runtime }); 1338 self.trigger('PostInit'); 1339 } else { 1340 self.trigger('Error', { 1341 code : plupload.INIT_ERROR, 1342 message : plupload.translate('Init error.') 1343 }); 1344 } 1345 }); 1346 } 1347 } 1348 1349 1350 // Internal event handlers 1351 function onBeforeUpload(up, file) { 1352 // Generate unique target filenames 1353 if (up.settings.unique_names) { 1354 var matches = file.name.match(/\.([^.]+)$/), ext = "part"; 1355 if (matches) { 1356 ext = matches[1]; 1357 } 1358 file.target_name = file.id + '.' + ext; 1359 } 1360 } 1361 1362 1363 function onUploadFile(up, file) { 1364 var url = up.settings.url 1365 , chunkSize = up.settings.chunk_size 1366 , retries = up.settings.max_retries 1367 , features = up.features 1368 , offset = 0 1369 , blob 1370 ; 1371 1372 // make sure we start at a predictable offset 1373 if (file.loaded) { 1374 offset = file.loaded = chunkSize ? chunkSize * Math.floor(file.loaded / chunkSize) : 0; 1375 } 1376 1377 function handleError() { 1378 if (retries-- > 0) { 1379 delay(uploadNextChunk, 1000); 1380 } else { 1381 file.loaded = offset; // reset all progress 1382 1383 up.trigger('Error', { 1384 code : plupload.HTTP_ERROR, 1385 message : plupload.translate('HTTP Error.'), 1386 file : file, 1387 response : xhr.responseText, 1388 status : xhr.status, 1389 responseHeaders: xhr.getAllResponseHeaders() 1390 }); 1391 } 1392 } 1393 1394 function uploadNextChunk() { 1395 var chunkBlob, formData, args = {}, curChunkSize; 1396 1397 // make sure that file wasn't cancelled and upload is not stopped in general 1398 if (file.status !== plupload.UPLOADING || up.state === plupload.STOPPED) { 1399 return; 1400 } 1401 1402 // send additional 'name' parameter only if required 1403 if (up.settings.send_file_name) { 1404 args.name = file.target_name || file.name; 1405 } 1406 1407 if (chunkSize && features.chunks && blob.size > chunkSize) { // blob will be of type string if it was loaded in memory 1408 curChunkSize = Math.min(chunkSize, blob.size - offset); 1409 chunkBlob = blob.slice(offset, offset + curChunkSize); 1410 } else { 1411 curChunkSize = blob.size; 1412 chunkBlob = blob; 1413 } 1414 1415 // If chunking is enabled add corresponding args, no matter if file is bigger than chunk or smaller 1416 if (chunkSize && features.chunks) { 1417 // Setup query string arguments 1418 if (up.settings.send_chunk_number) { 1419 args.chunk = Math.ceil(offset / chunkSize); 1420 args.chunks = Math.ceil(blob.size / chunkSize); 1421 } else { // keep support for experimental chunk format, just in case 1422 args.offset = offset; 1423 args.total = blob.size; 1424 } 1425 } 1426 1427 xhr = new o.XMLHttpRequest(); 1428 1429 // Do we have upload progress support 1430 if (xhr.upload) { 1431 xhr.upload.onprogress = function(e) { 1432 file.loaded = Math.min(file.size, offset + e.loaded); 1433 up.trigger('UploadProgress', file); 1434 }; 1435 } 1436 1437 xhr.onload = function() { 1438 // check if upload made itself through 1439 if (xhr.status >= 400) { 1440 handleError(); 1441 return; 1442 } 1443 1444 retries = up.settings.max_retries; // reset the counter 1445 1446 // Handle chunk response 1447 if (curChunkSize < blob.size) { 1448 chunkBlob.destroy(); 1449 1450 offset += curChunkSize; 1451 file.loaded = Math.min(offset, blob.size); 1452 1453 up.trigger('ChunkUploaded', file, { 1454 offset : file.loaded, 1455 total : blob.size, 1456 response : xhr.responseText, 1457 status : xhr.status, 1458 responseHeaders: xhr.getAllResponseHeaders() 1459 }); 1460 1461 // stock Android browser doesn't fire upload progress events, but in chunking mode we can fake them 1462 if (o.Env.browser === 'Android Browser') { 1463 // doesn't harm in general, but is not required anywhere else 1464 up.trigger('UploadProgress', file); 1465 } 1466 } else { 1467 file.loaded = file.size; 1468 } 1469 1470 chunkBlob = formData = null; // Free memory 1471 1472 // Check if file is uploaded 1473 if (!offset || offset >= blob.size) { 1474 // If file was modified, destory the copy 1475 if (file.size != file.origSize) { 1476 blob.destroy(); 1477 blob = null; 1478 } 1479 1480 up.trigger('UploadProgress', file); 1481 1482 file.status = plupload.DONE; 1483 1484 up.trigger('FileUploaded', file, { 1485 response : xhr.responseText, 1486 status : xhr.status, 1487 responseHeaders: xhr.getAllResponseHeaders() 1488 }); 1489 } else { 1490 // Still chunks left 1491 delay(uploadNextChunk, 1); // run detached, otherwise event handlers interfere 1492 } 1493 }; 1494 1495 xhr.onerror = function() { 1496 handleError(); 1497 }; 1498 1499 xhr.onloadend = function() { 1500 this.destroy(); 1501 xhr = null; 1502 }; 1503 1504 // Build multipart request 1505 if (up.settings.multipart && features.multipart) { 1506 xhr.open("post", url, true); 1507 1508 // Set custom headers 1509 plupload.each(up.settings.headers, function(value, name) { 1510 xhr.setRequestHeader(name, value); 1511 }); 1512 1513 formData = new o.FormData(); 1514 1515 // Add multipart params 1516 plupload.each(plupload.extend(args, up.settings.multipart_params), function(value, name) { 1517 formData.append(name, value); 1518 }); 1519 1520 // Add file and send it 1521 formData.append(up.settings.file_data_name, chunkBlob); 1522 xhr.send(formData, { 1523 runtime_order: up.settings.runtimes, 1524 required_caps: up.settings.required_features, 1525 preferred_caps: preferred_caps 1526 }); 1527 } else { 1528 // if no multipart, send as binary stream 1529 url = plupload.buildUrl(up.settings.url, plupload.extend(args, up.settings.multipart_params)); 1530 1531 xhr.open("post", url, true); 1532 1533 xhr.setRequestHeader('Content-Type', 'application/octet-stream'); // Binary stream header 1534 1535 // Set custom headers 1536 plupload.each(up.settings.headers, function(value, name) { 1537 xhr.setRequestHeader(name, value); 1538 }); 1539 1540 xhr.send(chunkBlob, { 1541 runtime_order: up.settings.runtimes, 1542 required_caps: up.settings.required_features, 1543 preferred_caps: preferred_caps 1544 }); 1545 } 1546 } 1547 1548 blob = file.getSource(); 1549 1550 // Start uploading chunks 1551 if (up.settings.resize.enabled && runtimeCan(blob, 'send_binary_string') && !!~o.inArray(blob.type, ['image/jpeg', 'image/png'])) { 1552 // Resize if required 1553 resizeImage.call(this, blob, up.settings.resize, function(resizedBlob) { 1554 blob = resizedBlob; 1555 file.size = resizedBlob.size; 1556 uploadNextChunk(); 1557 }); 1558 } else { 1559 uploadNextChunk(); 1560 } 1561 } 1562 1563 1564 function onUploadProgress(up, file) { 1565 calcFile(file); 1566 } 1567 1568 1569 function onStateChanged(up) { 1570 if (up.state == plupload.STARTED) { 1571 // Get start time to calculate bps 1572 startTime = (+new Date()); 1573 } else if (up.state == plupload.STOPPED) { 1574 // Reset currently uploading files 1575 for (var i = up.files.length - 1; i >= 0; i--) { 1576 if (up.files[i].status == plupload.UPLOADING) { 1577 up.files[i].status = plupload.QUEUED; 1578 calc(); 1579 } 1580 } 1581 } 1582 } 1583 1584 1585 function onCancelUpload() { 1586 if (xhr) { 1587 xhr.abort(); 1588 } 1589 } 1590 1591 1592 function onFileUploaded(up) { 1593 calc(); 1594 1595 // Upload next file but detach it from the error event 1596 // since other custom listeners might want to stop the queue 1597 delay(function() { 1598 uploadNext.call(up); 1599 }, 1); 1600 } 1601 1602 1603 function onError(up, err) { 1604 if (err.code === plupload.INIT_ERROR) { 1605 up.destroy(); 1606 } 1607 // Set failed status if an error occured on a file 1608 else if (err.code === plupload.HTTP_ERROR) { 1609 err.file.status = plupload.FAILED; 1610 calcFile(err.file); 1611 1612 // Upload next file but detach it from the error event 1613 // since other custom listeners might want to stop the queue 1614 if (up.state == plupload.STARTED) { // upload in progress 1615 up.trigger('CancelUpload'); 1616 delay(function() { 1617 uploadNext.call(up); 1618 }, 1); 1619 } 1620 } 1621 } 1622 1623 1624 function onDestroy(up) { 1625 up.stop(); 1626 1627 // Purge the queue 1628 plupload.each(files, function(file) { 1629 file.destroy(); 1630 }); 1631 files = []; 1632 1633 if (fileInputs.length) { 1634 plupload.each(fileInputs, function(fileInput) { 1635 fileInput.destroy(); 1636 }); 1637 fileInputs = []; 1638 } 1639 1640 if (fileDrops.length) { 1641 plupload.each(fileDrops, function(fileDrop) { 1642 fileDrop.destroy(); 1643 }); 1644 fileDrops = []; 1645 } 1646 1647 preferred_caps = {}; 1648 disabled = false; 1649 startTime = xhr = null; 1650 total.reset(); 1651 } 1652 1653 1654 // Default settings 1655 settings = { 1656 runtimes: o.Runtime.order, 1657 max_retries: 0, 1658 chunk_size: 0, 1659 multipart: true, 1660 multi_selection: true, 1661 file_data_name: 'file', 1662 filters: { 1663 mime_types: [], 1664 prevent_duplicates: false, 1665 max_file_size: 0 1666 }, 1667 resize: { 1668 enabled: false, 1669 preserve_headers: true, 1670 crop: false 1671 }, 1672 send_file_name: true, 1673 send_chunk_number: true 1674 }; 1675 1676 1677 setOption.call(this, options, null, true); 1678 1679 // Inital total state 1680 total = new plupload.QueueProgress(); 1681 1682 // Add public methods 1683 plupload.extend(this, { 1684 1685 /** 1686 * Unique id for the Uploader instance. 1687 * 1688 * @property id 1689 * @type String 1690 */ 1691 id : uid, 1692 uid : uid, // mOxie uses this to differentiate between event targets 1693 1694 /** 1695 * Current state of the total uploading progress. This one can either be plupload.STARTED or plupload.STOPPED. 1696 * These states are controlled by the stop/start methods. The default value is STOPPED. 1697 * 1698 * @property state 1699 * @type Number 1700 */ 1701 state : plupload.STOPPED, 1702 1703 /** 1704 * Map of features that are available for the uploader runtime. Features will be filled 1705 * before the init event is called, these features can then be used to alter the UI for the end user. 1706 * Some of the current features that might be in this map is: dragdrop, chunks, jpgresize, pngresize. 1707 * 1708 * @property features 1709 * @type Object 1710 */ 1711 features : {}, 1712 1713 /** 1714 * Current runtime name. 1715 * 1716 * @property runtime 1717 * @type String 1718 */ 1719 runtime : null, 1720 1721 /** 1722 * Current upload queue, an array of File instances. 1723 * 1724 * @property files 1725 * @type Array 1726 * @see plupload.File 1727 */ 1728 files : files, 1729 1730 /** 1731 * Object with name/value settings. 1732 * 1733 * @property settings 1734 * @type Object 1735 */ 1736 settings : settings, 1737 1738 /** 1739 * Total progess information. How many files has been uploaded, total percent etc. 1740 * 1741 * @property total 1742 * @type plupload.QueueProgress 1743 */ 1744 total : total, 1745 1746 1747 /** 1748 * Initializes the Uploader instance and adds internal event listeners. 1749 * 1750 * @method init 1751 */ 1752 init : function() { 1753 var self = this, opt, preinitOpt, err; 1754 1755 preinitOpt = self.getOption('preinit'); 1756 if (typeof(preinitOpt) == "function") { 1757 preinitOpt(self); 1758 } else { 1759 plupload.each(preinitOpt, function(func, name) { 1760 self.bind(name, func); 1761 }); 1762 } 1763 1764 bindEventListeners.call(self); 1765 1766 // Check for required options 1767 plupload.each(['container', 'browse_button', 'drop_element'], function(el) { 1768 if (self.getOption(el) === null) { 1769 err = { 1770 code : plupload.INIT_ERROR, 1771 message : plupload.translate("'%' specified, but cannot be found.") 1772 } 1773 return false; 1774 } 1775 }); 1776 1777 if (err) { 1778 return self.trigger('Error', err); 1779 } 1780 1781 1782 if (!settings.browse_button && !settings.drop_element) { 1783 return self.trigger('Error', { 1784 code : plupload.INIT_ERROR, 1785 message : plupload.translate("You must specify either 'browse_button' or 'drop_element'.") 1786 }); 1787 } 1788 1789 1790 initControls.call(self, settings, function(inited) { 1791 var initOpt = self.getOption('init'); 1792 if (typeof(initOpt) == "function") { 1793 initOpt(self); 1794 } else { 1795 plupload.each(initOpt, function(func, name) { 1796 self.bind(name, func); 1797 }); 1798 } 1799 1800 if (inited) { 1801 self.runtime = o.Runtime.getInfo(getRUID()).type; 1802 self.trigger('Init', { runtime: self.runtime }); 1803 self.trigger('PostInit'); 1804 } else { 1805 self.trigger('Error', { 1806 code : plupload.INIT_ERROR, 1807 message : plupload.translate('Init error.') 1808 }); 1809 } 1810 }); 1811 }, 1812 1813 /** 1814 * Set the value for the specified option(s). 1815 * 1816 * @method setOption 1817 * @since 2.1 1818 * @param {String|Object} option Name of the option to change or the set of key/value pairs 1819 * @param {Mixed} [value] Value for the option (is ignored, if first argument is object) 1820 */ 1821 setOption: function(option, value) { 1822 setOption.call(this, option, value, !this.runtime); // until runtime not set we do not need to reinitialize 1823 }, 1824 1825 /** 1826 * Get the value for the specified option or the whole configuration, if not specified. 1827 * 1828 * @method getOption 1829 * @since 2.1 1830 * @param {String} [option] Name of the option to get 1831 * @return {Mixed} Value for the option or the whole set 1832 */ 1833 getOption: function(option) { 1834 if (!option) { 1835 return settings; 1836 } 1837 return settings[option]; 1838 }, 1839 1840 /** 1841 * Refreshes the upload instance by dispatching out a refresh event to all runtimes. 1842 * This would for example reposition flash/silverlight shims on the page. 1843 * 1844 * @method refresh 1845 */ 1846 refresh : function() { 1847 if (fileInputs.length) { 1848 plupload.each(fileInputs, function(fileInput) { 1849 fileInput.trigger('Refresh'); 1850 }); 1851 } 1852 this.trigger('Refresh'); 1853 }, 1854 1855 /** 1856 * Starts uploading the queued files. 1857 * 1858 * @method start 1859 */ 1860 start : function() { 1861 if (this.state != plupload.STARTED) { 1862 this.state = plupload.STARTED; 1863 this.trigger('StateChanged'); 1864 1865 uploadNext.call(this); 1866 } 1867 }, 1868 1869 /** 1870 * Stops the upload of the queued files. 1871 * 1872 * @method stop 1873 */ 1874 stop : function() { 1875 if (this.state != plupload.STOPPED) { 1876 this.state = plupload.STOPPED; 1877 this.trigger('StateChanged'); 1878 this.trigger('CancelUpload'); 1879 } 1880 }, 1881 1882 1883 /** 1884 * Disables/enables browse button on request. 1885 * 1886 * @method disableBrowse 1887 * @param {Boolean} disable Whether to disable or enable (default: true) 1888 */ 1889 disableBrowse : function() { 1890 disabled = arguments[0] !== undef ? arguments[0] : true; 1891 1892 if (fileInputs.length) { 1893 plupload.each(fileInputs, function(fileInput) { 1894 fileInput.disable(disabled); 1895 }); 1896 } 1897 1898 this.trigger('DisableBrowse', disabled); 1899 }, 1900 1901 /** 1902 * Returns the specified file object by id. 1903 * 1904 * @method getFile 1905 * @param {String} id File id to look for. 1906 * @return {plupload.File} File object or undefined if it wasn't found; 1907 */ 1908 getFile : function(id) { 1909 var i; 1910 for (i = files.length - 1; i >= 0; i--) { 1911 if (files[i].id === id) { 1912 return files[i]; 1913 } 1914 } 1915 }, 1916 1917 /** 1918 * Adds file to the queue programmatically. Can be native file, instance of Plupload.File, 1919 * instance of mOxie.File, input[type="file"] element, or array of these. Fires FilesAdded, 1920 * if any files were added to the queue. Otherwise nothing happens. 1921 * 1922 * @method addFile 1923 * @since 2.0 1924 * @param {plupload.File|mOxie.File|File|Node|Array} file File or files to add to the queue. 1925 * @param {String} [fileName] If specified, will be used as a name for the file 1926 */ 1927 addFile : function(file, fileName) { 1928 var self = this 1929 , queue = [] 1930 , filesAdded = [] 1931 , ruid 1932 ; 1933 1934 function filterFile(file, cb) { 1935 var queue = []; 1936 o.each(self.settings.filters, function(rule, name) { 1937 if (fileFilters[name]) { 1938 queue.push(function(cb) { 1939 fileFilters[name].call(self, rule, file, function(res) { 1940 cb(!res); 1941 }); 1942 }); 1943 } 1944 }); 1945 o.inSeries(queue, cb); 1946 } 1947 1948 /** 1949 * @method resolveFile 1950 * @private 1951 * @param {o.File|o.Blob|plupload.File|File|Blob|input[type="file"]} file 1952 */ 1953 function resolveFile(file) { 1954 var type = o.typeOf(file); 1955 1956 // o.File 1957 if (file instanceof o.File) { 1958 if (!file.ruid && !file.isDetached()) { 1959 if (!ruid) { // weird case 1960 return false; 1961 } 1962 file.ruid = ruid; 1963 file.connectRuntime(ruid); 1964 } 1965 resolveFile(new plupload.File(file)); 1966 } 1967 // o.Blob 1968 else if (file instanceof o.Blob) { 1969 resolveFile(file.getSource()); 1970 file.destroy(); 1971 } 1972 // plupload.File - final step for other branches 1973 else if (file instanceof plupload.File) { 1974 if (fileName) { 1975 file.name = fileName; 1976 } 1977 1978 queue.push(function(cb) { 1979 // run through the internal and user-defined filters, if any 1980 filterFile(file, function(err) { 1981 if (!err) { 1982 // make files available for the filters by updating the main queue directly 1983 files.push(file); 1984 // collect the files that will be passed to FilesAdded event 1985 filesAdded.push(file); 1986 1987 self.trigger("FileFiltered", file); 1988 } 1989 delay(cb, 1); // do not build up recursions or eventually we might hit the limits 1990 }); 1991 }); 1992 } 1993 // native File or blob 1994 else if (o.inArray(type, ['file', 'blob']) !== -1) { 1995 resolveFile(new o.File(null, file)); 1996 } 1997 // input[type="file"] 1998 else if (type === 'node' && o.typeOf(file.files) === 'filelist') { 1999 // if we are dealing with input[type="file"] 2000 o.each(file.files, resolveFile); 2001 } 2002 // mixed array of any supported types (see above) 2003 else if (type === 'array') { 2004 fileName = null; // should never happen, but unset anyway to avoid funny situations 2005 o.each(file, resolveFile); 2006 } 2007 } 2008 2009 ruid = getRUID(); 2010 2011 resolveFile(file); 2012 2013 if (queue.length) { 2014 o.inSeries(queue, function() { 2015 // if any files left after filtration, trigger FilesAdded 2016 if (filesAdded.length) { 2017 self.trigger("FilesAdded", filesAdded); 2018 } 2019 }); 2020 } 2021 }, 2022 2023 /** 2024 * Removes a specific file. 2025 * 2026 * @method removeFile 2027 * @param {plupload.File|String} file File to remove from queue. 2028 */ 2029 removeFile : function(file) { 2030 var id = typeof(file) === 'string' ? file : file.id; 2031 2032 for (var i = files.length - 1; i >= 0; i--) { 2033 if (files[i].id === id) { 2034 return this.splice(i, 1)[0]; 2035 } 2036 } 2037 }, 2038 2039 /** 2040 * Removes part of the queue and returns the files removed. This will also trigger the FilesRemoved and QueueChanged events. 2041 * 2042 * @method splice 2043 * @param {Number} start (Optional) Start index to remove from. 2044 * @param {Number} length (Optional) Lengh of items to remove. 2045 * @return {Array} Array of files that was removed. 2046 */ 2047 splice : function(start, length) { 2048 // Splice and trigger events 2049 var removed = files.splice(start === undef ? 0 : start, length === undef ? files.length : length); 2050 2051 // if upload is in progress we need to stop it and restart after files are removed 2052 var restartRequired = false; 2053 if (this.state == plupload.STARTED) { // upload in progress 2054 plupload.each(removed, function(file) { 2055 if (file.status === plupload.UPLOADING) { 2056 restartRequired = true; // do not restart, unless file that is being removed is uploading 2057 return false; 2058 } 2059 }); 2060 2061 if (restartRequired) { 2062 this.stop(); 2063 } 2064 } 2065 2066 this.trigger("FilesRemoved", removed); 2067 2068 // Dispose any resources allocated by those files 2069 plupload.each(removed, function(file) { 2070 file.destroy(); 2071 }); 2072 2073 if (restartRequired) { 2074 this.start(); 2075 } 2076 2077 return removed; 2078 }, 2079 2080 /** 2081 Dispatches the specified event name and its arguments to all listeners. 2082 2083 @method trigger 2084 @param {String} name Event name to fire. 2085 @param {Object..} Multiple arguments to pass along to the listener functions. 2086 */ 2087 2088 // override the parent method to match Plupload-like event logic 2089 dispatchEvent: function(type) { 2090 var list, args, result; 2091 2092 type = type.toLowerCase(); 2093 2094 list = this.hasEventListener(type); 2095 2096 if (list) { 2097 // sort event list by priority 2098 list.sort(function(a, b) { return b.priority - a.priority; }); 2099 2100 // first argument should be current plupload.Uploader instance 2101 args = [].slice.call(arguments); 2102 args.shift(); 2103 args.unshift(this); 2104 2105 for (var i = 0; i < list.length; i++) { 2106 // Fire event, break chain if false is returned 2107 if (list[i].fn.apply(list[i].scope, args) === false) { 2108 return false; 2109 } 2110 } 2111 } 2112 return true; 2113 }, 2114 2115 /** 2116 Check whether uploader has any listeners to the specified event. 2117 2118 @method hasEventListener 2119 @param {String} name Event name to check for. 2120 */ 2121 2122 2123 /** 2124 Adds an event listener by name. 2125 2126 @method bind 2127 @param {String} name Event name to listen for. 2128 @param {function} fn Function to call ones the event gets fired. 2129 @param {Object} [scope] Optional scope to execute the specified function in. 2130 @param {Number} [priority=0] Priority of the event handler - handlers with higher priorities will be called first 2131 */ 2132 bind: function(name, fn, scope, priority) { 2133 // adapt moxie EventTarget style to Plupload-like 2134 plupload.Uploader.prototype.bind.call(this, name, fn, priority, scope); 2135 }, 2136 2137 /** 2138 Removes the specified event listener. 2139 2140 @method unbind 2141 @param {String} name Name of event to remove. 2142 @param {function} fn Function to remove from listener. 2143 */ 2144 2145 /** 2146 Removes all event listeners. 2147 2148 @method unbindAll 2149 */ 2150 2151 2152 /** 2153 * Destroys Plupload instance and cleans after itself. 2154 * 2155 * @method destroy 2156 */ 2157 destroy : function() { 2158 this.trigger('Destroy'); 2159 settings = total = null; // purge these exclusively 2160 this.unbindAll(); 2161 } 2162 }); 2163 }; 2164 2165 plupload.Uploader.prototype = o.EventTarget.instance; 2166 2167 /** 2168 * Constructs a new file instance. 2169 * 2170 * @class File 2171 * @constructor 2172 * 2173 * @param {Object} file Object containing file properties 2174 * @param {String} file.name Name of the file. 2175 * @param {Number} file.size File size. 2176 */ 2177 plupload.File = (function() { 2178 var filepool = {}; 2179 2180 function PluploadFile(file) { 2181 2182 plupload.extend(this, { 2183 2184 /** 2185 * File id this is a globally unique id for the specific file. 2186 * 2187 * @property id 2188 * @type String 2189 */ 2190 id: plupload.guid(), 2191 2192 /** 2193 * File name for example "myfile.gif". 2194 * 2195 * @property name 2196 * @type String 2197 */ 2198 name: file.name || file.fileName, 2199 2200 /** 2201 * File type, `e.g image/jpeg` 2202 * 2203 * @property type 2204 * @type String 2205 */ 2206 type: file.type || '', 2207 2208 /** 2209 * File size in bytes (may change after client-side manupilation). 2210 * 2211 * @property size 2212 * @type Number 2213 */ 2214 size: file.size || file.fileSize, 2215 2216 /** 2217 * Original file size in bytes. 2218 * 2219 * @property origSize 2220 * @type Number 2221 */ 2222 origSize: file.size || file.fileSize, 2223 2224 /** 2225 * Number of bytes uploaded of the files total size. 2226 * 2227 * @property loaded 2228 * @type Number 2229 */ 2230 loaded: 0, 2231 2232 /** 2233 * Number of percentage uploaded of the file. 2234 * 2235 * @property percent 2236 * @type Number 2237 */ 2238 percent: 0, 2239 2240 /** 2241 * Status constant matching the plupload states QUEUED, UPLOADING, FAILED, DONE. 2242 * 2243 * @property status 2244 * @type Number 2245 * @see plupload 2246 */ 2247 status: plupload.QUEUED, 2248 2249 /** 2250 * Date of last modification. 2251 * 2252 * @property lastModifiedDate 2253 * @type {String} 2254 */ 2255 lastModifiedDate: file.lastModifiedDate || (new Date()).toLocaleString(), // Thu Aug 23 2012 19:40:00 GMT+0400 (GET) 2256 2257 /** 2258 * Returns native window.File object, when it's available. 2259 * 2260 * @method getNative 2261 * @return {window.File} or null, if plupload.File is of different origin 2262 */ 2263 getNative: function() { 2264 var file = this.getSource().getSource(); 2265 return o.inArray(o.typeOf(file), ['blob', 'file']) !== -1 ? file : null; 2266 }, 2267 2268 /** 2269 * Returns mOxie.File - unified wrapper object that can be used across runtimes. 2270 * 2271 * @method getSource 2272 * @return {mOxie.File} or null 2273 */ 2274 getSource: function() { 2275 if (!filepool[this.id]) { 2276 return null; 2277 } 2278 return filepool[this.id]; 2279 }, 2280 2281 /** 2282 * Destroys plupload.File object. 2283 * 2284 * @method destroy 2285 */ 2286 destroy: function() { 2287 var src = this.getSource(); 2288 if (src) { 2289 src.destroy(); 2290 delete filepool[this.id]; 2291 } 2292 } 2293 }); 2294 2295 filepool[this.id] = file; 2296 } 2297 2298 return PluploadFile; 2299 }()); 2300 2301 2302 /** 2303 * Constructs a queue progress. 2304 * 2305 * @class QueueProgress 2306 * @constructor 2307 */ 2308 plupload.QueueProgress = function() { 2309 var self = this; // Setup alias for self to reduce code size when it's compressed 2310 2311 /** 2312 * Total queue file size. 2313 * 2314 * @property size 2315 * @type Number 2316 */ 2317 self.size = 0; 2318 2319 /** 2320 * Total bytes uploaded. 2321 * 2322 * @property loaded 2323 * @type Number 2324 */ 2325 self.loaded = 0; 2326 2327 /** 2328 * Number of files uploaded. 2329 * 2330 * @property uploaded 2331 * @type Number 2332 */ 2333 self.uploaded = 0; 2334 2335 /** 2336 * Number of files failed to upload. 2337 * 2338 * @property failed 2339 * @type Number 2340 */ 2341 self.failed = 0; 2342 2343 /** 2344 * Number of files yet to be uploaded. 2345 * 2346 * @property queued 2347 * @type Number 2348 */ 2349 self.queued = 0; 2350 2351 /** 2352 * Total percent of the uploaded bytes. 2353 * 2354 * @property percent 2355 * @type Number 2356 */ 2357 self.percent = 0; 2358 2359 /** 2360 * Bytes uploaded per second. 2361 * 2362 * @property bytesPerSec 2363 * @type Number 2364 */ 2365 self.bytesPerSec = 0; 2366 2367 /** 2368 * Resets the progress to its initial values. 2369 * 2370 * @method reset 2371 */ 2372 self.reset = function() { 2373 self.size = self.loaded = self.uploaded = self.failed = self.queued = self.percent = self.bytesPerSec = 0; 2374 }; 2375 }; 2376 2377 window.plupload = plupload; 2378 2379 }(window, mOxie));
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Nov 21 01:00:03 2024 | Cross-referenced by PHPXref 0.7.1 |