PageRenderTime 69ms CodeModel.GetById 10ms app.highlight 47ms RepoModel.GetById 0ms app.codeStats 1ms

/wp-includes/js/plupload/plupload.js

https://bitbucket.org/skyarch-iijima/wordpress
JavaScript | 2379 lines | 2086 code | 45 blank | 248 comment | 14 complexity | dda0aa24705a5218d13e271c8c187cf7 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file