plupload.dev.js ➔ onUploadFile   F
last analyzed

Complexity

Conditions 30

Size

Total Lines 210
Code Lines 117

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 117
dl 0
loc 210
c 0
b 0
f 0
rs 0
cc 30

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like plupload.dev.js ➔ onUploadFile often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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