PageSourceSearch

https://devourtours.com/wp-includes/js/plupload/plupload.js

js devourtours.com collected 2026-09-25 04:08:37 UTC 60,314 bytes, 2,379 lines download raw bytes

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
32var delay = window.setTimeout
33, fileFilters = {}
34;
35
36// convert plupload features to caps acceptable by mOxie
37function 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 */
96var 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
668plupload.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
682plupload.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
701plupload.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*/
754plupload.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				}
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
2165plupload.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 */
2177plupload.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
2377window.plupload = plupload;
2378
2379}(window, mOxie));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.