Test Failed
Push — main ( 83e7c8...0c12e7 )
by chief
09:12
created

plupload.dev.js ➔ setOption   F

Complexity

Conditions 26

Size

Total Lines 154
Code Lines 100

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 100
dl 0
loc 154
c 0
b 0
f 0
rs 0
cc 26

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 ➔ setOption 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) {
1 ignored issue
show
Bug introduced by
The variable define seems to be never declared. If this is a global, consider adding a /** global: define */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
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) {
1 ignored issue
show
Bug introduced by
The variable undef seems to be never initialized.
Loading history...
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;
1 ignored issue
show
Unused Code introduced by
The variable opt seems to be never used. Consider removing it.
Loading history...
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) {
1 ignored issue
show
Complexity Best Practice introduced by
There is no return statement if self.getOption(el) === null is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
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
			});
1 ignored issue
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
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
			}
1 ignored issue
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
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));
1 ignored issue
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
2063
				}
2064
				// moxie.file.Blob
2065
				else if (file instanceof o.file.Blob) {
2066
					resolveFile(file.getSource());
2067
					file.destroy();
1 ignored issue
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
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
					});
1 ignored issue
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
2089
				}
2090
				// native File or blob
2091
				else if (plupload.inArray(type, ['file', 'blob']) !== -1) {
2092
					resolveFile(new o.file.File(null, file));
1 ignored issue
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
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);
1 ignored issue
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
2098
				}
2099
				// mixed array of any supported types (see above)
2100
				else if (type === 'array') {
1 ignored issue
show
Complexity Best Practice introduced by
There is no return statement if type === "array" is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
2101
					fileName = null; // should never happen, but unset anyway to avoid funny situations
2102
					plupload.each(file, resolveFile);
1 ignored issue
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
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
			}
1 ignored issue
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
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) {
1 ignored issue
show
Complexity Best Practice introduced by
There is no return statement if file.status === plupload.UPLOADING is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
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;
1 ignored issue
show
Unused Code introduced by
The variable result seems to be never used. Consider removing it.
Loading history...
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
}));