Proyectos de Subversion LeadersLinked - Antes de SPA

Rev

| Ultima modificación | Ver Log |

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