Subversion-Projekte lars-tiefland.ci

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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