Project

General

Profile

Download (58.5 KB) Statistics
| Branch: | Revision:
1
/**
2
 * Plupload - multi-runtime File Uploader
3
 * v2.1.8
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: 2015-07-21
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.8',
98

    
99
	/**
100
	 * The state of the queue before it has started and after it has finished
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 millisecond 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 asteriod. 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 its 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|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.
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 just before a file is uploaded. Can be used to cancel the upload for the specified file
820
	by returning false from the handler.
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} result Object with response properties.
850
		@param {Number} result.offset The amount of bytes the server has received so far, including this chunk.
851
		@param {Number} result.total The size of the file.
852
		@param {String} result.response The response body sent by the server.
853
		@param {Number} result.status The HTTP status code sent by the server.
854
		@param {String} result.responseHeaders All the response headers as a single string.
855
	 */
856

    
857
	/**
858
	Fires when a file is successfully uploaded.
859
	
860
	@event FileUploaded
861
	@param {plupload.Uploader} uploader Uploader instance sending the event.
862
	@param {plupload.File} file File that was uploaded.
863
	@param {Object} result Object with response properties.
864
		@param {String} result.response The response body sent by the server.
865
		@param {Number} result.status The HTTP status code sent by the server.
866
		@param {String} result.responseHeaders All the response headers as a single string.
867
	 */
868

    
869
	/**
870
	Fires when all files in a queue are uploaded.
871
	
872
	@event UploadComplete
873
	@param {plupload.Uploader} uploader Uploader instance sending the event.
874
	@param {Array} files Array of file objects that was added to queue/selected by the user.
875
	 */
876

    
877
	/**
878
	Fires when a error occurs.
879
	
880
	@event Error
881
	@param {plupload.Uploader} uploader Uploader instance sending the event.
882
	@param {Object} error Contains code, message and sometimes file and other details.
883
		@param {Number} error.code The plupload error code.
884
		@param {String} error.message Description of the error (uses i18n).
885
	 */
886

    
887
	/**
888
	Fires when destroy method is called.
889
	
890
	@event Destroy
891
	@param {plupload.Uploader} uploader Uploader instance sending the event.
892
	 */
893
	var uid = plupload.guid()
894
	, settings
895
	, files = []
896
	, preferred_caps = {}
897
	, fileInputs = []
898
	, fileDrops = []
899
	, startTime
900
	, total
901
	, disabled = false
902
	, xhr
903
	;
904

    
905

    
906
	// Private methods
907
	function uploadNext() {
908
		var file, count = 0, i;
909

    
910
		if (this.state == plupload.STARTED) {
911
			// Find first QUEUED file
912
			for (i = 0; i < files.length; i++) {
913
				if (!file && files[i].status == plupload.QUEUED) {
914
					file = files[i];
915
					if (this.trigger("BeforeUpload", file)) {
916
						file.status = plupload.UPLOADING;
917
						this.trigger("UploadFile", file);
918
					}
919
				} else {
920
					count++;
921
				}
922
			}
923

    
924
			// All files are DONE or FAILED
925
			if (count == files.length) {
926
				if (this.state !== plupload.STOPPED) {
927
					this.state = plupload.STOPPED;
928
					this.trigger("StateChanged");
929
				}
930
				this.trigger("UploadComplete", files);
931
			}
932
		}
933
	}
934

    
935

    
936
	function calcFile(file) {
937
		file.percent = file.size > 0 ? Math.ceil(file.loaded / file.size * 100) : 100;
938
		calc();
939
	}
940

    
941

    
942
	function calc() {
943
		var i, file;
944

    
945
		// Reset stats
946
		total.reset();
947

    
948
		// Check status, size, loaded etc on all files
949
		for (i = 0; i < files.length; i++) {
950
			file = files[i];
951

    
952
			if (file.size !== undef) {
953
				file.origSize = parseInt(file.origSize); // Clearly a patch
954
				file.size = parseInt(file.size);
955
				// We calculate totals based on original file size
956
				total.size += file.origSize;
957

    
958
				// Since we cannot predict file size after resize, we do opposite and
959
				// interpolate loaded amount to match magnitude of total
960
				total.loaded += file.loaded * file.origSize / file.size;
961
			} else {
962
				total.size = undef;
963
			}
964

    
965
			if (file.status == plupload.DONE) {
966
				total.uploaded++;
967
			} else if (file.status == plupload.FAILED) {
968
				total.failed++;
969
			} else {
970
				total.queued++;
971
			}
972
		}
973

    
974
		// If we couldn't calculate a total file size then use the number of files to calc percent
975
		if (total.size === undef) {
976
			total.percent = files.length > 0 ? Math.ceil(total.uploaded / files.length * 100) : 0;
977
		} else {
978
			total.bytesPerSec = Math.ceil(total.loaded / ((+new Date() - startTime || 1) / 1000.0));
979
			total.percent = total.size > 0 ? Math.ceil(total.loaded / total.size * 100) : 0;
980
		}
981
	}
982

    
983

    
984
	function getRUID() {
985
		var ctrl = fileInputs[0] || fileDrops[0];
986
		if (ctrl) {
987
			return ctrl.getRuntime().uid;
988
		}
989
		return false;
990
	}
991

    
992

    
993
	function runtimeCan(file, cap) {
994
		if (file.ruid) {
995
			var info = o.Runtime.getInfo(file.ruid);
996
			if (info) {
997
				return info.can(cap);
998
			}
999
		}
1000
		return false;
1001
	}
1002

    
1003

    
1004
	function bindEventListeners() {
1005
		this.bind('FilesAdded FilesRemoved', function(up) {
1006
			up.trigger('QueueChanged');
1007
			up.refresh();
1008
		});
1009

    
1010
		this.bind('CancelUpload', onCancelUpload);
1011
		
1012
		this.bind('BeforeUpload', onBeforeUpload);
1013

    
1014
		this.bind('UploadFile', onUploadFile);
1015

    
1016
		this.bind('UploadProgress', onUploadProgress);
1017

    
1018
		this.bind('StateChanged', onStateChanged);
1019

    
1020
		this.bind('QueueChanged', calc);
1021

    
1022
		this.bind('Error', onError);
1023

    
1024
		this.bind('FileUploaded', onFileUploaded);
1025

    
1026
		this.bind('Destroy', onDestroy);
1027
	}
1028

    
1029

    
1030
	function initControls(settings, cb) {
1031
		var self = this, inited = 0, queue = [];
1032

    
1033
		// common settings
1034
		var options = {
1035
			runtime_order: settings.runtimes,
1036
			required_caps: settings.required_features,
1037
			preferred_caps: preferred_caps,
1038
			swf_url: settings.flash_swf_url,
1039
			xap_url: settings.silverlight_xap_url
1040
		};
1041

    
1042
		// add runtime specific options if any
1043
		plupload.each(settings.runtimes.split(/\s*,\s*/), function(runtime) {
1044
			if (settings[runtime]) {
1045
				options[runtime] = settings[runtime];
1046
			}
1047
		});
1048

    
1049
		// initialize file pickers - there can be many
1050
		if (settings.browse_button) {
1051
			plupload.each(settings.browse_button, function(el) {
1052
				queue.push(function(cb) {
1053
					var fileInput = new o.FileInput(plupload.extend({}, options, {
1054
						accept: settings.filters.mime_types,
1055
						name: settings.file_data_name,
1056
						multiple: settings.multi_selection,
1057
						container: settings.container,
1058
						browse_button: el
1059
					}));
1060

    
1061
					fileInput.onready = function() {
1062
						var info = o.Runtime.getInfo(this.ruid);
1063

    
1064
						// for backward compatibility
1065
						o.extend(self.features, {
1066
							chunks: info.can('slice_blob'),
1067
							multipart: info.can('send_multipart'),
1068
							multi_selection: info.can('select_multiple')
1069
						});
1070

    
1071
						inited++;
1072
						fileInputs.push(this);
1073
						cb();
1074
					};
1075

    
1076
					fileInput.onchange = function() {
1077
						self.addFile(this.files);
1078
					};
1079

    
1080
					fileInput.bind('mouseenter mouseleave mousedown mouseup', function(e) {
1081
						if (!disabled) {
1082
							if (settings.browse_button_hover) {
1083
								if ('mouseenter' === e.type) {
1084
									o.addClass(el, settings.browse_button_hover);
1085
								} else if ('mouseleave' === e.type) {
1086
									o.removeClass(el, settings.browse_button_hover);
1087
								}
1088
							}
1089

    
1090
							if (settings.browse_button_active) {
1091
								if ('mousedown' === e.type) {
1092
									o.addClass(el, settings.browse_button_active);
1093
								} else if ('mouseup' === e.type) {
1094
									o.removeClass(el, settings.browse_button_active);
1095
								}
1096
							}
1097
						}
1098
					});
1099

    
1100
					fileInput.bind('mousedown', function() {
1101
						self.trigger('Browse');
1102
					});
1103

    
1104
					fileInput.bind('error runtimeerror', function() {
1105
						fileInput = null;
1106
						cb();
1107
					});
1108

    
1109
					fileInput.init();
1110
				});
1111
			});
1112
		}
1113

    
1114
		// initialize drop zones
1115
		if (settings.drop_element) {
1116
			plupload.each(settings.drop_element, function(el) {
1117
				queue.push(function(cb) {
1118
					var fileDrop = new o.FileDrop(plupload.extend({}, options, {
1119
						drop_zone: el
1120
					}));
1121

    
1122
					fileDrop.onready = function() {
1123
						var info = o.Runtime.getInfo(this.ruid);
1124

    
1125
						self.features.dragdrop = info.can('drag_and_drop'); // for backward compatibility
1126

    
1127
						inited++;
1128
						fileDrops.push(this);
1129
						cb();
1130
					};
1131

    
1132
					fileDrop.ondrop = function() {
1133
						self.addFile(this.files);
1134
					};
1135

    
1136
					fileDrop.bind('error runtimeerror', function() {
1137
						fileDrop = null;
1138
						cb();
1139
					});
1140

    
1141
					fileDrop.init();
1142
				});
1143
			});
1144
		}
1145

    
1146

    
1147
		o.inSeries(queue, function() {
1148
			if (typeof(cb) === 'function') {
1149
				cb(inited);
1150
			}
1151
		});
1152
	}
1153

    
1154

    
1155
	function resizeImage(blob, params, cb) {
1156
		var img = new o.Image();
1157

    
1158
		try {
1159
			img.onload = function() {
1160
				// no manipulation required if...
1161
				if (params.width > this.width &&
1162
					params.height > this.height &&
1163
					params.quality === undef &&
1164
					params.preserve_headers &&
1165
					!params.crop
1166
				) {
1167
					this.destroy();
1168
					return cb(blob);
1169
				}
1170
				// otherwise downsize
1171
				img.downsize(params.width, params.height, params.crop, params.preserve_headers);
1172
			};
1173

    
1174
			img.onresize = function() {
1175
				cb(this.getAsBlob(blob.type, params.quality));
1176
				this.destroy();
1177
			};
1178

    
1179
			img.onerror = function() {
1180
				cb(blob);
1181
			};
1182

    
1183
			img.load(blob);
1184
		} catch(ex) {
1185
			cb(blob);
1186
		}
1187
	}
1188

    
1189

    
1190
	function setOption(option, value, init) {
1191
		var self = this, reinitRequired = false;
1192

    
1193
		function _setOption(option, value, init) {
1194
			var oldValue = settings[option];
1195

    
1196
			switch (option) {
1197
				case 'max_file_size':
1198
					if (option === 'max_file_size') {
1199
						settings.max_file_size = settings.filters.max_file_size = value;
1200
					}
1201
					break;
1202

    
1203
				case 'chunk_size':
1204
					if (value = plupload.parseSize(value)) {
1205
						settings[option] = value;
1206
						settings.send_file_name = true;
1207
					}
1208
					break;
1209

    
1210
				case 'multipart':
1211
					settings[option] = value;
1212
					if (!value) {
1213
						settings.send_file_name = true;
1214
					}
1215
					break;
1216

    
1217
				case 'unique_names':
1218
					settings[option] = value;
1219
					if (value) {
1220
						settings.send_file_name = true;
1221
					}
1222
					break;
1223

    
1224
				case 'filters':
1225
					// for sake of backward compatibility
1226
					if (plupload.typeOf(value) === 'array') {
1227
						value = {
1228
							mime_types: value
1229
						};
1230
					}
1231

    
1232
					if (init) {
1233
						plupload.extend(settings.filters, value);
1234
					} else {
1235
						settings.filters = value;
1236
					}
1237

    
1238
					// if file format filters are being updated, regenerate the matching expressions
1239
					if (value.mime_types) {
1240
						settings.filters.mime_types.regexp = (function(filters) {
1241
							var extensionsRegExp = [];
1242

    
1243
							plupload.each(filters, function(filter) {
1244
								plupload.each(filter.extensions.split(/,/), function(ext) {
1245
									if (/^\s*\*\s*$/.test(ext)) {
1246
										extensionsRegExp.push('\\.*');
1247
									} else {
1248
										extensionsRegExp.push('\\.' + ext.replace(new RegExp('[' + ('/^$.*+?|()[]{}\\'.replace(/./g, '\\$&')) + ']', 'g'), '\\$&'));
1249
									}
1250
								});
1251
							});
1252

    
1253
							return new RegExp('(' + extensionsRegExp.join('|') + ')$', 'i');
1254
						}(settings.filters.mime_types));
1255
					}
1256
					break;
1257
	
1258
				case 'resize':
1259
					if (init) {
1260
						plupload.extend(settings.resize, value, {
1261
							enabled: true
1262
						});
1263
					} else {
1264
						settings.resize = value;
1265
					}
1266
					break;
1267

    
1268
				case 'prevent_duplicates':
1269
					settings.prevent_duplicates = settings.filters.prevent_duplicates = !!value;
1270
					break;
1271

    
1272
				case 'browse_button':
1273
				case 'drop_element':
1274
						value = plupload.get(value);
1275

    
1276
				case 'container':
1277
				case 'runtimes':
1278
				case 'multi_selection':
1279
				case 'flash_swf_url':
1280
				case 'silverlight_xap_url':
1281
					settings[option] = value;
1282
					if (!init) {
1283
						reinitRequired = true;
1284
					}
1285
					break;
1286

    
1287
				default:
1288
					settings[option] = value;
1289
			}
1290

    
1291
			if (!init) {
1292
				self.trigger('OptionChanged', option, value, oldValue);
1293
			}
1294
		}
1295

    
1296
		if (typeof(option) === 'object') {
1297
			plupload.each(option, function(value, option) {
1298
				_setOption(option, value, init);
1299
			});
1300
		} else {
1301
			_setOption(option, value, init);
1302
		}
1303

    
1304
		if (init) {
1305
			// Normalize the list of required capabilities
1306
			settings.required_features = normalizeCaps(plupload.extend({}, settings));
1307

    
1308
			// Come up with the list of capabilities that can affect default mode in a multi-mode runtimes
1309
			preferred_caps = normalizeCaps(plupload.extend({}, settings, {
1310
				required_features: true
1311
			}));
1312
		} else if (reinitRequired) {
1313
			self.trigger('Destroy');
1314
			
1315
			initControls.call(self, settings, function(inited) {
1316
				if (inited) {
1317
					self.runtime = o.Runtime.getInfo(getRUID()).type;
1318
					self.trigger('Init', { runtime: self.runtime });
1319
					self.trigger('PostInit');
1320
				} else {
1321
					self.trigger('Error', {
1322
						code : plupload.INIT_ERROR,
1323
						message : plupload.translate('Init error.')
1324
					});
1325
				}
1326
			});
1327
		}
1328
	}
1329

    
1330

    
1331
	// Internal event handlers
1332
	function onBeforeUpload(up, file) {
1333
		// Generate unique target filenames
1334
		if (up.settings.unique_names) {
1335
			var matches = file.name.match(/\.([^.]+)$/), ext = "part";
1336
			if (matches) {
1337
				ext = matches[1];
1338
			}
1339
			file.target_name = file.id + '.' + ext;
1340
		}
1341
	}
1342

    
1343

    
1344
	function onUploadFile(up, file) {
1345
		var url = up.settings.url
1346
		, chunkSize = up.settings.chunk_size
1347
		, retries = up.settings.max_retries
1348
		, features = up.features
1349
		, offset = 0
1350
		, blob
1351
		;
1352

    
1353
		// make sure we start at a predictable offset
1354
		if (file.loaded) {
1355
			offset = file.loaded = chunkSize ? chunkSize * Math.floor(file.loaded / chunkSize) : 0;
1356
		}
1357

    
1358
		function handleError() {
1359
			if (retries-- > 0) {
1360
				delay(uploadNextChunk, 1000);
1361
			} else {
1362
				file.loaded = offset; // reset all progress
1363

    
1364
				up.trigger('Error', {
1365
					code : plupload.HTTP_ERROR,
1366
					message : plupload.translate('HTTP Error.'),
1367
					file : file,
1368
					response : xhr.responseText,
1369
					status : xhr.status,
1370
					responseHeaders: xhr.getAllResponseHeaders()
1371
				});
1372
			}
1373
		}
1374

    
1375
		function uploadNextChunk() {
1376
            if (file.url) { // This is a download
1377
                console.log("This is a download!");
1378
                return;
1379
            }
1380

    
1381
			var chunkBlob, formData, args = {}, curChunkSize;
1382

    
1383
			// make sure that file wasn't cancelled and upload is not stopped in general
1384
			if (file.status !== plupload.UPLOADING || up.state === plupload.STOPPED) {
1385
				return;
1386
			}
1387

    
1388
			// send additional 'name' parameter only if required
1389
			if (up.settings.send_file_name) {
1390
				args.name = file.target_name || file.name;
1391
			}
1392

    
1393
			if (chunkSize && features.chunks && blob.size > chunkSize) { // blob will be of type string if it was loaded in memory 
1394
				curChunkSize = Math.min(chunkSize, blob.size - offset);
1395
				chunkBlob = blob.slice(offset, offset + curChunkSize);
1396
			} else {
1397
				curChunkSize = blob.size;
1398
				chunkBlob = blob;
1399
			}
1400

    
1401
			// If chunking is enabled add corresponding args, no matter if file is bigger than chunk or smaller
1402
			if (chunkSize && features.chunks) {
1403
				// Setup query string arguments
1404
				if (up.settings.send_chunk_number) {
1405
					args.chunk = Math.ceil(offset / chunkSize);
1406
					args.chunks = Math.ceil(blob.size / chunkSize);
1407
				} else { // keep support for experimental chunk format, just in case
1408
					args.offset = offset;
1409
					args.total = blob.size;
1410
				}
1411
			}
1412

    
1413
			xhr = new o.XMLHttpRequest();
1414

    
1415
			// Do we have upload progress support
1416
			if (xhr.upload) {
1417
				xhr.upload.onprogress = function(e) {
1418
					file.loaded = Math.min(file.size, offset + e.loaded);
1419
					up.trigger('UploadProgress', file);
1420
				};
1421
			}
1422

    
1423
			xhr.onload = function() {
1424
				// check if upload made itself through
1425
				if (xhr.status >= 400) {
1426
					handleError();
1427
					return;
1428
				}
1429

    
1430
				retries = up.settings.max_retries; // reset the counter
1431

    
1432
				// Handle chunk response
1433
				if (curChunkSize < blob.size) {
1434
					chunkBlob.destroy();
1435

    
1436
					offset += curChunkSize;
1437
					file.loaded = Math.min(offset, blob.size);
1438

    
1439
					up.trigger('ChunkUploaded', file, {
1440
						offset : file.loaded,
1441
						total : blob.size,
1442
						response : xhr.responseText,
1443
						status : xhr.status,
1444
						responseHeaders: xhr.getAllResponseHeaders()
1445
					});
1446

    
1447
					// stock Android browser doesn't fire upload progress events, but in chunking mode we can fake them
1448
					if (o.Env.browser === 'Android Browser') {
1449
						// doesn't harm in general, but is not required anywhere else
1450
						up.trigger('UploadProgress', file);
1451
					} 
1452
				} else {
1453
					file.loaded = file.size;
1454
				}
1455

    
1456
				chunkBlob = formData = null; // Free memory
1457

    
1458
				// Check if file is uploaded
1459
				if (!offset || offset >= blob.size) {
1460
					// If file was modified, destory the copy
1461
					if (file.size != file.origSize) {
1462
						blob.destroy();
1463
						blob = null;
1464
					}
1465

    
1466
					up.trigger('UploadProgress', file);
1467

    
1468
					file.status = plupload.DONE;
1469

    
1470
					up.trigger('FileUploaded', file, {
1471
						response : xhr.responseText,
1472
						status : xhr.status,
1473
						responseHeaders: xhr.getAllResponseHeaders()
1474
					});
1475
				} else {
1476
					// Still chunks left
1477
					delay(uploadNextChunk, 1); // run detached, otherwise event handlers interfere
1478
				}
1479
			};
1480

    
1481
			xhr.onerror = function() {
1482
				handleError();
1483
			};
1484

    
1485
			xhr.onloadend = function() {
1486
				this.destroy();
1487
				xhr = null;
1488
			};
1489

    
1490
			// Build multipart request
1491
			if (up.settings.multipart && features.multipart) {
1492
				xhr.open("post", url, true);
1493

    
1494
				// Set custom headers
1495
				plupload.each(up.settings.headers, function(value, name) {
1496
					xhr.setRequestHeader(name, value);
1497
				});
1498

    
1499
				formData = new o.FormData();
1500

    
1501
				// Add multipart params
1502
				plupload.each(plupload.extend(args, up.settings.multipart_params), function(value, name) {
1503
					formData.append(name, value);
1504
				});
1505

    
1506
				// Add file and send it
1507
				formData.append(up.settings.file_data_name, chunkBlob);
1508
				xhr.send(formData, {
1509
					runtime_order: up.settings.runtimes,
1510
					required_caps: up.settings.required_features,
1511
					preferred_caps: preferred_caps,
1512
					swf_url: up.settings.flash_swf_url,
1513
					xap_url: up.settings.silverlight_xap_url
1514
				});
1515
			} else {
1516
				// if no multipart, send as binary stream
1517
				url = plupload.buildUrl(up.settings.url, plupload.extend(args, up.settings.multipart_params));
1518

    
1519
				xhr.open("post", url, true);
1520

    
1521
				xhr.setRequestHeader('Content-Type', 'application/octet-stream'); // Binary stream header
1522

    
1523
				// Set custom headers
1524
				plupload.each(up.settings.headers, function(value, name) {
1525
					xhr.setRequestHeader(name, value);
1526
				});
1527

    
1528
				xhr.send(chunkBlob, {
1529
					runtime_order: up.settings.runtimes,
1530
					required_caps: up.settings.required_features,
1531
					preferred_caps: preferred_caps,
1532
					swf_url: up.settings.flash_swf_url,
1533
					xap_url: up.settings.silverlight_xap_url
1534
				});
1535
			}
1536
		}
1537

    
1538
		blob = file.getSource();
1539

    
1540
		// Start uploading chunks
1541
		if (up.settings.resize.enabled && runtimeCan(blob, 'send_binary_string') && !!~o.inArray(blob.type, ['image/jpeg', 'image/png'])) {
1542
			// Resize if required
1543
			resizeImage.call(this, blob, up.settings.resize, function(resizedBlob) {
1544
				blob = resizedBlob;
1545
				file.size = resizedBlob.size;
1546
				uploadNextChunk();
1547
			});
1548
		} else {
1549
			uploadNextChunk();
1550
		}
1551
	}
1552

    
1553

    
1554
	function onUploadProgress(up, file) {
1555
		calcFile(file);
1556
	}
1557

    
1558

    
1559
	function onStateChanged(up) {
1560
		if (up.state == plupload.STARTED) {
1561
			// Get start time to calculate bps
1562
			startTime = (+new Date());
1563
		} else if (up.state == plupload.STOPPED) {
1564
			// Reset currently uploading files
1565
			for (var i = up.files.length - 1; i >= 0; i--) {
1566
				if (up.files[i].status == plupload.UPLOADING) {
1567
					up.files[i].status = plupload.QUEUED;
1568
					calc();
1569
				}
1570
			}
1571
		}
1572
	}
1573

    
1574

    
1575
	function onCancelUpload() {
1576
		if (xhr) {
1577
			xhr.abort();
1578
		}
1579
	}
1580

    
1581

    
1582
	function onFileUploaded(up) {
1583
		calc();
1584

    
1585
		// Upload next file but detach it from the error event
1586
		// since other custom listeners might want to stop the queue
1587
		delay(function() {
1588
			uploadNext.call(up);
1589
		}, 1);
1590
	}
1591

    
1592

    
1593
	function onError(up, err) {
1594
		if (err.code === plupload.INIT_ERROR) {
1595
			up.destroy();
1596
		}
1597
		// Set failed status if an error occured on a file
1598
		else if (err.code === plupload.HTTP_ERROR) {
1599
			err.file.status = plupload.FAILED;
1600
			calcFile(err.file);
1601

    
1602
			// Upload next file but detach it from the error event
1603
			// since other custom listeners might want to stop the queue
1604
			if (up.state == plupload.STARTED) { // upload in progress
1605
				up.trigger('CancelUpload');
1606
				delay(function() {
1607
					uploadNext.call(up);
1608
				}, 1);
1609
			}
1610
		}
1611
	}
1612

    
1613

    
1614
	function onDestroy(up) {
1615
		up.stop();
1616

    
1617
		// Purge the queue
1618
		plupload.each(files, function(file) {
1619
			file.destroy();
1620
		});
1621
		files = [];
1622

    
1623
		if (fileInputs.length) {
1624
			plupload.each(fileInputs, function(fileInput) {
1625
				fileInput.destroy();
1626
			});
1627
			fileInputs = [];
1628
		}
1629

    
1630
		if (fileDrops.length) {
1631
			plupload.each(fileDrops, function(fileDrop) {
1632
				fileDrop.destroy();
1633
			});
1634
			fileDrops = [];
1635
		}
1636

    
1637
		preferred_caps = {};
1638
		disabled = false;
1639
		startTime = xhr = null;
1640
		total.reset();
1641
	}
1642

    
1643

    
1644
	// Default settings
1645
	settings = {
1646
		runtimes: o.Runtime.order,
1647
		max_retries: 0,
1648
		chunk_size: 0,
1649
		multipart: true,
1650
		multi_selection: true,
1651
		file_data_name: 'file',
1652
		flash_swf_url: 'js/Moxie.swf',
1653
		silverlight_xap_url: 'js/Moxie.xap',
1654
		filters: {
1655
			mime_types: [],
1656
			prevent_duplicates: false,
1657
			max_file_size: 0
1658
		},
1659
		resize: {
1660
			enabled: false,
1661
			preserve_headers: true,
1662
			crop: false
1663
		},
1664
		send_file_name: true,
1665
		send_chunk_number: true
1666
	};
1667

    
1668
	
1669
	setOption.call(this, options, null, true);
1670

    
1671
	// Inital total state
1672
	total = new plupload.QueueProgress(); 
1673

    
1674
	// Add public methods
1675
	plupload.extend(this, {
1676

    
1677
		/**
1678
		 * Unique id for the Uploader instance.
1679
		 *
1680
		 * @property id
1681
		 * @type String
1682
		 */
1683
		id : uid,
1684
		uid : uid, // mOxie uses this to differentiate between event targets
1685

    
1686
		/**
1687
		 * Current state of the total uploading progress. This one can either be plupload.STARTED or plupload.STOPPED.
1688
		 * These states are controlled by the stop/start methods. The default value is STOPPED.
1689
		 *
1690
		 * @property state
1691
		 * @type Number
1692
		 */
1693
		state : plupload.STOPPED,
1694

    
1695
		/**
1696
		 * Map of features that are available for the uploader runtime. Features will be filled
1697
		 * before the init event is called, these features can then be used to alter the UI for the end user.
1698
		 * Some of the current features that might be in this map is: dragdrop, chunks, jpgresize, pngresize.
1699
		 *
1700
		 * @property features
1701
		 * @type Object
1702
		 */
1703
		features : {},
1704

    
1705
		/**
1706
		 * Current runtime name.
1707
		 *
1708
		 * @property runtime
1709
		 * @type String
1710
		 */
1711
		runtime : null,
1712

    
1713
		/**
1714
		 * Current upload queue, an array of File instances.
1715
		 *
1716
		 * @property files
1717
		 * @type Array
1718
		 * @see plupload.File
1719
		 */
1720
		files : files,
1721

    
1722
		/**
1723
		 * Object with name/value settings.
1724
		 *
1725
		 * @property settings
1726
		 * @type Object
1727
		 */
1728
		settings : settings,
1729

    
1730
		/**
1731
		 * Total progess information. How many files has been uploaded, total percent etc.
1732
		 *
1733
		 * @property total
1734
		 * @type plupload.QueueProgress
1735
		 */
1736
		total : total,
1737

    
1738

    
1739
		/**
1740
		 * Initializes the Uploader instance and adds internal event listeners.
1741
		 *
1742
		 * @method init
1743
		 */
1744
		init : function() {
1745
			var self = this;
1746

    
1747
			if (typeof(settings.preinit) == "function") {
1748
				settings.preinit(self);
1749
			} else {
1750
				plupload.each(settings.preinit, function(func, name) {
1751
					self.bind(name, func);
1752
				});
1753
			}
1754

    
1755
			bindEventListeners.call(this);
1756

    
1757
			// Check for required options
1758
			if (!settings.browse_button || !settings.url) {
1759
				this.trigger('Error', {
1760
					code : plupload.INIT_ERROR,
1761
					message : plupload.translate('Init error.')
1762
				});
1763
				return;
1764
			}
1765

    
1766
			initControls.call(this, settings, function(inited) {
1767
				if (typeof(settings.init) == "function") {
1768
					settings.init(self);
1769
				} else {
1770
					plupload.each(settings.init, function(func, name) {
1771
						self.bind(name, func);
1772
					});
1773
				}
1774

    
1775
				if (inited) {
1776
					self.runtime = o.Runtime.getInfo(getRUID()).type;
1777
					self.trigger('Init', { runtime: self.runtime });
1778
					self.trigger('PostInit');
1779
				} else {
1780
					self.trigger('Error', {
1781
						code : plupload.INIT_ERROR,
1782
						message : plupload.translate('Init error.')
1783
					});
1784
				}
1785
			});
1786
		},
1787

    
1788
		/**
1789
		 * Set the value for the specified option(s).
1790
		 *
1791
		 * @method setOption
1792
		 * @since 2.1
1793
		 * @param {String|Object} option Name of the option to change or the set of key/value pairs
1794
		 * @param {Mixed} [value] Value for the option (is ignored, if first argument is object)
1795
		 */
1796
		setOption: function(option, value) {
1797
			setOption.call(this, option, value, !this.runtime); // until runtime not set we do not need to reinitialize
1798
		},
1799

    
1800
		/**
1801
		 * Get the value for the specified option or the whole configuration, if not specified.
1802
		 * 
1803
		 * @method getOption
1804
		 * @since 2.1
1805
		 * @param {String} [option] Name of the option to get
1806
		 * @return {Mixed} Value for the option or the whole set
1807
		 */
1808
		getOption: function(option) {
1809
			if (!option) {
1810
				return settings;
1811
			}
1812
			return settings[option];
1813
		},
1814

    
1815
		/**
1816
		 * Refreshes the upload instance by dispatching out a refresh event to all runtimes.
1817
		 * This would for example reposition flash/silverlight shims on the page.
1818
		 *
1819
		 * @method refresh
1820
		 */
1821
		refresh : function() {
1822
			if (fileInputs.length) {
1823
				plupload.each(fileInputs, function(fileInput) {
1824
					fileInput.trigger('Refresh');
1825
				});
1826
			}
1827
			this.trigger('Refresh');
1828
		},
1829

    
1830
		/**
1831
		 * Starts uploading the queued files.
1832
		 *
1833
		 * @method start
1834
		 */
1835
		start : function() {
1836
			if (this.state != plupload.STARTED) {
1837
				this.state = plupload.STARTED;
1838
				this.trigger('StateChanged');
1839

    
1840
				uploadNext.call(this);
1841
			}
1842
		},
1843

    
1844
		/**
1845
		 * Stops the upload of the queued files.
1846
		 *
1847
		 * @method stop
1848
		 */
1849
		stop : function() {
1850
			if (this.state != plupload.STOPPED) {
1851
				this.state = plupload.STOPPED;
1852
				this.trigger('StateChanged');
1853
				this.trigger('CancelUpload');
1854
			}
1855
		},
1856

    
1857

    
1858
		/**
1859
		 * Disables/enables browse button on request.
1860
		 *
1861
		 * @method disableBrowse
1862
		 * @param {Boolean} disable Whether to disable or enable (default: true)
1863
		 */
1864
		disableBrowse : function() {
1865
			disabled = arguments[0] !== undef ? arguments[0] : true;
1866

    
1867
			if (fileInputs.length) {
1868
				plupload.each(fileInputs, function(fileInput) {
1869
					fileInput.disable(disabled);
1870
				});
1871
			}
1872

    
1873
			this.trigger('DisableBrowse', disabled);
1874
		},
1875

    
1876
		/**
1877
		 * Returns the specified file object by id.
1878
		 *
1879
		 * @method getFile
1880
		 * @param {String} id File id to look for.
1881
		 * @return {plupload.File} File object or undefined if it wasn't found;
1882
		 */
1883
		getFile : function(id) {
1884
			var i;
1885
			for (i = files.length - 1; i >= 0; i--) {
1886
				if (files[i].id === id) {
1887
					return files[i];
1888
				}
1889
			}
1890
		},
1891

    
1892
		/**
1893
		 * Adds file to the queue programmatically. Can be native file, instance of Plupload.File,
1894
		 * instance of mOxie.File, input[type="file"] element, or array of these. Fires FilesAdded, 
1895
		 * if any files were added to the queue. Otherwise nothing happens.
1896
		 *
1897
		 * @method addFile
1898
		 * @since 2.0
1899
		 * @param {plupload.File|mOxie.File|File|Node|Array} file File or files to add to the queue.
1900
		 * @param {String} [fileName] If specified, will be used as a name for the file
1901
		 */
1902
		addFile : function(file, fileName) {
1903
			var self = this
1904
			, queue = [] 
1905
			, filesAdded = []
1906
			, ruid
1907
			;
1908

    
1909
			function filterFile(file, cb) {
1910
				var queue = [];
1911
				o.each(self.settings.filters, function(rule, name) {
1912
					if (fileFilters[name]) {
1913
						queue.push(function(cb) {
1914
							fileFilters[name].call(self, rule, file, function(res) {
1915
								cb(!res);
1916
							});
1917
						});
1918
					}
1919
				});
1920
				o.inSeries(queue, cb);
1921
			}
1922

    
1923
			/**
1924
			 * @method resolveFile
1925
			 * @private
1926
			 * @param {o.File|o.Blob|plupload.File|File|Blob|input[type="file"]} file
1927
			 */
1928
			function resolveFile(file) {
1929
				var type = o.typeOf(file);
1930

    
1931
				// o.File
1932
				if (file instanceof o.File) { 
1933
					if (!file.ruid && !file.isDetached()) {
1934
						if (!ruid) { // weird case
1935
							return false;
1936
						}
1937
						file.ruid = ruid;
1938
						file.connectRuntime(ruid);
1939
					}
1940
					resolveFile(new plupload.File(file));
1941
				}
1942
				// o.Blob 
1943
				else if (file instanceof o.Blob) {
1944
					resolveFile(file.getSource());
1945
					file.destroy();
1946
				} 
1947
				// plupload.File - final step for other branches
1948
				else if (file instanceof plupload.File) {
1949
					if (fileName) {
1950
						file.name = fileName;
1951
					}
1952
					
1953
					queue.push(function(cb) {
1954
						// run through the internal and user-defined filters, if any
1955
						filterFile(file, function(err) {
1956
							if (!err) {
1957
								// make files available for the filters by updating the main queue directly
1958
								files.push(file);
1959
								// collect the files that will be passed to FilesAdded event
1960
								filesAdded.push(file); 
1961

    
1962
								self.trigger("FileFiltered", file);
1963
							}
1964
							delay(cb, 1); // do not build up recursions or eventually we might hit the limits
1965
						});
1966
					});
1967
				} 
1968
				// native File or blob
1969
				else if (o.inArray(type, ['file', 'blob']) !== -1) {
1970
					resolveFile(new o.File(null, file));
1971
				} 
1972
				// input[type="file"]
1973
				else if (type === 'node' && o.typeOf(file.files) === 'filelist') {
1974
					// if we are dealing with input[type="file"]
1975
					o.each(file.files, resolveFile);
1976
				} 
1977
				// mixed array of any supported types (see above)
1978
				else if (type === 'array') {
1979
					fileName = null; // should never happen, but unset anyway to avoid funny situations
1980
					o.each(file, resolveFile);
1981
				}
1982
			}
1983

    
1984
			ruid = getRUID();
1985
			
1986
			resolveFile(file);
1987

    
1988
			if (queue.length) {
1989
				o.inSeries(queue, function() {
1990
					// if any files left after filtration, trigger FilesAdded
1991
					if (filesAdded.length) {
1992
						self.trigger("FilesAdded", filesAdded);
1993
					}
1994
				});
1995
			}
1996
		},
1997

    
1998
		/**
1999
		 * Removes a specific file.
2000
		 *
2001
		 * @method removeFile
2002
		 * @param {plupload.File|String} file File to remove from queue.
2003
		 */
2004
		removeFile : function(file) {
2005
			var id = typeof(file) === 'string' ? file : file.id;
2006

    
2007
			for (var i = files.length - 1; i >= 0; i--) {
2008
				if (files[i].id === id) {
2009
					return this.splice(i, 1)[0];
2010
				}
2011
			}
2012
		},
2013

    
2014
		/**
2015
		 * Removes part of the queue and returns the files removed. This will also trigger the FilesRemoved and QueueChanged events.
2016
		 *
2017
		 * @method splice
2018
		 * @param {Number} start (Optional) Start index to remove from.
2019
		 * @param {Number} length (Optional) Lengh of items to remove.
2020
		 * @return {Array} Array of files that was removed.
2021
		 */
2022
		splice : function(start, length) {
2023
			// Splice and trigger events
2024
			var removed = files.splice(start === undef ? 0 : start, length === undef ? files.length : length);
2025

    
2026
			// if upload is in progress we need to stop it and restart after files are removed
2027
			var restartRequired = false;
2028
			if (this.state == plupload.STARTED) { // upload in progress
2029
				plupload.each(removed, function(file) {
2030
					if (file.status === plupload.UPLOADING) {
2031
						restartRequired = true; // do not restart, unless file that is being removed is uploading
2032
						return false;
2033
					}
2034
				});
2035
				
2036
				if (restartRequired) {
2037
					this.stop();
2038
				}
2039
			}
2040

    
2041
			this.trigger("FilesRemoved", removed);
2042

    
2043
			// Dispose any resources allocated by those files
2044
			plupload.each(removed, function(file) {
2045
				file.destroy();
2046
			});
2047
			
2048
			if (restartRequired) {
2049
				this.start();
2050
			}
2051

    
2052
			return removed;
2053
		},
2054

    
2055
		/**
2056
		Dispatches the specified event name and its arguments to all listeners.
2057

    
2058
		@method trigger
2059
		@param {String} name Event name to fire.
2060
		@param {Object..} Multiple arguments to pass along to the listener functions.
2061
		*/
2062

    
2063
		// override the parent method to match Plupload-like event logic
2064
		dispatchEvent: function(type) {
2065
			var list, args, result;
2066
						
2067
			type = type.toLowerCase();
2068
							
2069
			list = this.hasEventListener(type);
2070

    
2071
			if (list) {
2072
				// sort event list by prority
2073
				list.sort(function(a, b) { return b.priority - a.priority; });
2074
				
2075
				// first argument should be current plupload.Uploader instance
2076
				args = [].slice.call(arguments);
2077
				args.shift();
2078
				args.unshift(this);
2079

    
2080
				for (var i = 0; i < list.length; i++) {
2081
					// Fire event, break chain if false is returned
2082
					if (list[i].fn.apply(list[i].scope, args) === false) {
2083
						return false;
2084
					}
2085
				}
2086
			}
2087
			return true;
2088
		},
2089

    
2090
		/**
2091
		Check whether uploader has any listeners to the specified event.
2092

    
2093
		@method hasEventListener
2094
		@param {String} name Event name to check for.
2095
		*/
2096

    
2097

    
2098
		/**
2099
		Adds an event listener by name.
2100

    
2101
		@method bind
2102
		@param {String} name Event name to listen for.
2103
		@param {function} fn Function to call ones the event gets fired.
2104
		@param {Object} [scope] Optional scope to execute the specified function in.
2105
		@param {Number} [priority=0] Priority of the event handler - handlers with higher priorities will be called first
2106
		*/
2107
		bind: function(name, fn, scope, priority) {
2108
			// adapt moxie EventTarget style to Plupload-like
2109
			plupload.Uploader.prototype.bind.call(this, name, fn, priority, scope);
2110
		},
2111

    
2112
		/**
2113
		Removes the specified event listener.
2114

    
2115
		@method unbind
2116
		@param {String} name Name of event to remove.
2117
		@param {function} fn Function to remove from listener.
2118
		*/
2119

    
2120
		/**
2121
		Removes all event listeners.
2122

    
2123
		@method unbindAll
2124
		*/
2125

    
2126

    
2127
		/**
2128
		 * Destroys Plupload instance and cleans after itself.
2129
		 *
2130
		 * @method destroy
2131
		 */
2132
		destroy : function() {
2133
			this.trigger('Destroy');
2134
			settings = total = null; // purge these exclusively
2135
			this.unbindAll();
2136
		}
2137
	});
2138
};
2139

    
2140
plupload.Uploader.prototype = o.EventTarget.instance;
2141

    
2142
/**
2143
 * Constructs a new file instance.
2144
 *
2145
 * @class File
2146
 * @constructor
2147
 * 
2148
 * @param {Object} file Object containing file properties
2149
 * @param {String} file.name Name of the file.
2150
 * @param {Number} file.size File size.
2151
 */
2152
plupload.File = (function() {
2153
	var filepool = {};
2154

    
2155
	function PluploadFile(file) {
2156

    
2157
		plupload.extend(this, {
2158

    
2159
			/**
2160
			 * File id this is a globally unique id for the specific file.
2161
			 *
2162
			 * @property id
2163
			 * @type String
2164
			 */
2165
			id: plupload.guid(),
2166

    
2167
			/**
2168
			 * File name for example "myfile.gif".
2169
			 *
2170
			 * @property name
2171
			 * @type String
2172
			 */
2173
			name: file.name || file.fileName,
2174

    
2175
			/**
2176
			 * File type, `e.g image/jpeg`
2177
			 *
2178
			 * @property type
2179
			 * @type String
2180
			 */
2181
			type: file.type || '',
2182

    
2183
			/**
2184
			 * File size in bytes (may change after client-side manupilation).
2185
			 *
2186
			 * @property size
2187
			 * @type Number
2188
			 */
2189
			size: file.size || file.fileSize,
2190

    
2191
			/**
2192
			 * Original file size in bytes.
2193
			 *
2194
			 * @property origSize
2195
			 * @type Number
2196
			 */
2197
			origSize: file.size || file.fileSize,
2198

    
2199
			/**
2200
			 * Number of bytes uploaded of the files total size.
2201
			 *
2202
			 * @property loaded
2203
			 * @type Number
2204
			 */
2205
			loaded: 0,
2206

    
2207
			/**
2208
			 * Number of percentage uploaded of the file.
2209
			 *
2210
			 * @property percent
2211
			 * @type Number
2212
			 */
2213
			percent: 0,
2214

    
2215
			/**
2216
			 * Status constant matching the plupload states QUEUED, UPLOADING, FAILED, DONE.
2217
			 *
2218
			 * @property status
2219
			 * @type Number
2220
			 * @see plupload
2221
			 */
2222
			status: plupload.QUEUED,
2223

    
2224
			/**
2225
			 * Date of last modification.
2226
			 *
2227
			 * @property lastModifiedDate
2228
			 * @type {String}
2229
			 */
2230
			lastModifiedDate: file.lastModifiedDate || (new Date()).toLocaleString(), // Thu Aug 23 2012 19:40:00 GMT+0400 (GET)
2231

    
2232
			/**
2233
			 * Returns native window.File object, when it's available.
2234
			 *
2235
			 * @method getNative
2236
			 * @return {window.File} or null, if plupload.File is of different origin
2237
			 */
2238
			getNative: function() {
2239
				var file = this.getSource().getSource();
2240
				return o.inArray(o.typeOf(file), ['blob', 'file']) !== -1 ? file : null;
2241
			},
2242

    
2243
			/**
2244
			 * Returns mOxie.File - unified wrapper object that can be used across runtimes.
2245
			 *
2246
			 * @method getSource
2247
			 * @return {mOxie.File} or null
2248
			 */
2249
			getSource: function() {
2250
				if (!filepool[this.id]) {
2251
					return null;
2252
				}
2253
				return filepool[this.id];
2254
			},
2255

    
2256
			/**
2257
			 * Destroys plupload.File object.
2258
			 *
2259
			 * @method destroy
2260
			 */
2261
			destroy: function() {
2262
				var src = this.getSource();
2263
				if (src) {
2264
					src.destroy();
2265
					delete filepool[this.id];
2266
				}
2267
			}
2268
		});
2269

    
2270
		filepool[this.id] = file;
2271
	}
2272

    
2273
	return PluploadFile;
2274
}());
2275

    
2276

    
2277
/**
2278
 * Constructs a queue progress.
2279
 *
2280
 * @class QueueProgress
2281
 * @constructor
2282
 */
2283
 plupload.QueueProgress = function() {
2284
	var self = this; // Setup alias for self to reduce code size when it's compressed
2285

    
2286
	/**
2287
	 * Total queue file size.
2288
	 *
2289
	 * @property size
2290
	 * @type Number
2291
	 */
2292
	self.size = 0;
2293

    
2294
	/**
2295
	 * Total bytes uploaded.
2296
	 *
2297
	 * @property loaded
2298
	 * @type Number
2299
	 */
2300
	self.loaded = 0;
2301

    
2302
	/**
2303
	 * Number of files uploaded.
2304
	 *
2305
	 * @property uploaded
2306
	 * @type Number
2307
	 */
2308
	self.uploaded = 0;
2309

    
2310
	/**
2311
	 * Number of files failed to upload.
2312
	 *
2313
	 * @property failed
2314
	 * @type Number
2315
	 */
2316
	self.failed = 0;
2317

    
2318
	/**
2319
	 * Number of files yet to be uploaded.
2320
	 *
2321
	 * @property queued
2322
	 * @type Number
2323
	 */
2324
	self.queued = 0;
2325

    
2326
	/**
2327
	 * Total percent of the uploaded bytes.
2328
	 *
2329
	 * @property percent
2330
	 * @type Number
2331
	 */
2332
	self.percent = 0;
2333

    
2334
	/**
2335
	 * Bytes uploaded per second.
2336
	 *
2337
	 * @property bytesPerSec
2338
	 * @type Number
2339
	 */
2340
	self.bytesPerSec = 0;
2341

    
2342
	/**
2343
	 * Resets the progress to its initial values.
2344
	 *
2345
	 * @method reset
2346
	 */
2347
	self.reset = function() {
2348
		self.size = self.loaded = self.uploaded = self.failed = self.queued = self.percent = self.bytesPerSec = 0;
2349
	};
2350
};
2351

    
2352
window.plupload = plupload;
2353

    
2354
}(window, mOxie));
(3-3/4)