/**
 * Plupload - multi-runtime File Uploader
 * v3.1.2
 *
 * Copyright 2018, Ephox
 * Released under AGPLv3 License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 *
 * Date: 2018-02-20
 */
;(function (global, factory) {
	var extract = function() {
		var ctx = {};
		factory.apply(ctx, arguments);
		return ctx.plupload;
	};
	
	if (typeof define === "function" && define.amd) {
		define("plupload", ['./moxie'], extract);
	} else if (typeof module === "object" && module.exports) {
		module.exports = extract(require('./moxie'));
	} else {
		global.plupload = extract(global.moxie);
	}
}(this || window, function(moxie) {
/**
 * Compiled inline version. (Library mode)
 */

/*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */
/*globals $code */

(function(exports, undefined) {
	"use strict";

	var modules = {};

	function require(ids, callback) {
		var module, defs = [];

		for (var i = 0; i < ids.length; ++i) {
			module = modules[ids[i]] || resolve(ids[i]);
			if (!module) {
				throw 'module definition dependecy not found: ' + ids[i];
			}

			defs.push(module);
		}

		callback.apply(null, defs);
	}

	function define(id, dependencies, definition) {
		if (typeof id !== 'string') {
			throw 'invalid module definition, module id must be defined and be a string';
		}

		if (dependencies === undefined) {
			throw 'invalid module definition, dependencies must be specified';
		}

		if (definition === undefined) {
			throw 'invalid module definition, definition function must be specified';
		}

		require(dependencies, function() {
			modules[id] = definition.apply(null, arguments);
		});
	}

	function defined(id) {
		return !!modules[id];
	}

	function resolve(id) {
		var target = exports;
		var fragments = id.split(/[.\/]/);

		for (var fi = 0; fi < fragments.length; ++fi) {
			if (!target[fragments[fi]]) {
				return;
			}

			target = target[fragments[fi]];
		}

		return target;
	}

	function expose(ids) {
		for (var i = 0; i < ids.length; i++) {
			var target = exports;
			var id = ids[i];
			var fragments = id.split(/[.\/]/);

			for (var fi = 0; fi < fragments.length - 1; ++fi) {
				if (target[fragments[fi]] === undefined) {
					target[fragments[fi]] = {};
				}

				target = target[fragments[fi]];
			}

			target[fragments[fragments.length - 1]] = modules[id];
		}
	}

// Included from: src/plupload.js

/**
 * plupload.js
 *
 * Copyright 2017, Ephox
 * Released under AGPLv3 License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

/**
Namespace for all Plupload related classes, methods and properties.

@class plupload
@public
@static
*/
define('plupload', [], function() {

	var o = moxie;
	var u = o.core.utils;

	// redifine event dispatcher for Flash/Silverlight runtimes
	u.Env.global_event_dispatcher = 'plupload.EventTarget.instance.dispatchEvent';


	return {
		/**
		 * Plupload version will be replaced on build.
		 *
		 * @property VERSION
		 * @static
		 * @final
		 */
		VERSION: '3.1.2',

		/**
		 * The state of the queue before it has started and after it has finished
		 *
		 * @property STOPPED
		 * @static
		 * @final
		 */
		STOPPED: 1,

		/**
		 * Upload process is running
		 *
		 * @property STARTED
		 * @static
		 * @final
		 */
		STARTED: 2,

		/**
		File is queued for upload

		@property QUEUED
		@static
		@final
		*/
		QUEUED: 1,

		/**
		File is being uploaded

		@property UPLOADING
		@static
		@final
		 */
		UPLOADING: 2,

		/**
		File has failed to be uploaded

		@property FAILED
		@static
		@final
		 */
		FAILED: 4,

		/**
		File has been uploaded successfully

		@property DONE
		@static
		@final
		 */
		DONE: 5,

		// Error constants used by the Error event

		/**
		 * Generic error for example if an exception is thrown inside Silverlight.
		 *
		 * @property GENERIC_ERROR
		 * @static
		 * @final
		 */
		GENERIC_ERROR: -100,

		/**
		 * HTTP transport error. For example if the server produces a HTTP status other than 200.
		 *
		 * @property HTTP_ERROR
		 * @static
		 * @final
		 */
		HTTP_ERROR: -200,

		/**
		 * Generic I/O error. For example if it wasn't possible to open the file stream on local machine.
		 *
		 * @property IO_ERROR
		 * @static
		 * @final
		 */
		IO_ERROR: -300,

		/**
		 * @property SECURITY_ERROR
		 * @static
		 * @final
		 */
		SECURITY_ERROR: -400,

		/**
		 * Initialization error. Will be triggered if no runtime was initialized.
		 *
		 * @property INIT_ERROR
		 * @static
		 * @final
		 */
		INIT_ERROR: -500,

		/**
		 * 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.
		 *
		 * @property FILE_SIZE_ERROR
		 * @static
		 * @final
		 */
		FILE_SIZE_ERROR: -600,

		/**
		 * File extension error. If the user selects a file that isn't valid according to the filters setting.
		 *
		 * @property FILE_EXTENSION_ERROR
		 * @static
		 * @final
		 */
		FILE_EXTENSION_ERROR: -601,

		/**
		 * Duplicate file error. If prevent_duplicates is set to true and user selects the same file again.
		 *
		 * @property FILE_DUPLICATE_ERROR
		 * @static
		 * @final
		 */
		FILE_DUPLICATE_ERROR: -602,

		/**
		 * Runtime will try to detect if image is proper one. Otherwise will throw this error.
		 *
		 * @property IMAGE_FORMAT_ERROR
		 * @static
		 * @final
		 */
		IMAGE_FORMAT_ERROR: -700,

		/**
		 * While working on files runtime may run out of memory and will throw this error.
		 *
		 * @since 2.1.2
		 * @property MEMORY_ERROR
		 * @static
		 * @final
		 */
		MEMORY_ERROR: -701,

		/**
		 * Each runtime has an upper limit on a dimension of the image it can handle. If bigger, will throw this error.
		 *
		 * @property IMAGE_DIMENSIONS_ERROR
		 * @static
		 * @final
		 */
		IMAGE_DIMENSIONS_ERROR: -702,


		/**
		Invalid option error. Will be thrown if user tries to alter the option that cannot be changed without
		uploader reinitialisation.

		@property OPTION_ERROR
		@static
		@final
		*/
		OPTION_ERROR: -800,

		/**
		 * Expose whole moxie (#1469).
		 *
		 * @property moxie
		 * @type Object
		 * @final
		 */
		moxie: o,

		/**
		 * In some cases sniffing is the only way around :(
		 */
		ua: u.Env,

		/**
		 * Gets the true type of the built-in object (better version of typeof).
		 * @credits Angus Croll (http://javascriptweblog.wordpress.com/)
		 *
		 * @method typeOf
		 * @static
		 * @param {Object} o Object to check.
		 * @return {String} Object [[Class]]
		 */
		typeOf: u.Basic.typeOf,

		clone: u.Basic.clone,

		inherit: u.Basic.inherit,


		/**
		 * Extends the specified object with another object.
		 *
		 * @method extend
		 * @static
		 * @param {Object} target Object to extend.
		 * @param {Object..} obj Multiple objects to extend with.
		 * @return {Object} Same as target, the extended object.
		 */
		extend: u.Basic.extend,


		extendImmutable: u.Basic.extendImmutable,

		/**
		Extends the specified object with another object(s), but only if the property exists in the target.

		@method extendIf
		@static
		@param {Object} target Object to extend.
		@param {Object} [obj]* Multiple objects to extend with.
		@return {Object} Same as target, the extended object.
		*/
		extendIf: u.Basic.extendIf,

		/**
		Recieve an array of functions (usually async) to call in sequence, each  function
		receives a callback as first argument that it should call, when it completes. Finally,
		after everything is complete, main callback is called. Passing truthy value to the
		callback as a first argument will interrupt the sequence and invoke main callback
		immediately.

		@method inSeries
		@static
		@param {Array} queue Array of functions to call in sequence
		@param {Function} cb Main callback that is called in the end, or in case of error
		*/
		inSeries: u.Basic.inSeries,

		/**
		Recieve an array of functions (usually async) to call in parallel, each  function
		receives a callback as first argument that it should call, when it completes. After
		everything is complete, main callback is called. Passing truthy value to the
		callback as a first argument will interrupt the process and invoke main callback
		immediately.

		@method inParallel
		@static
		@param {Array} queue Array of functions to call in sequence
		@param {Function} cb Main callback that is called in the end, or in case of erro
		*/
		inParallel: u.Basic.inParallel,

		/**
		 * Generates an unique ID. This is 99.99% unique since it takes the current time and 5 random numbers.
		 * The only way a user would be able to get the same ID is if the two persons at the same exact millisecond manages
		 * 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.
		 * 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
		 * to an user unique key.
		 *
		 * @method guid
		 * @static
		 * @return {String} Virtually unique id.
		 */
		guid: u.Basic.guid,

		/**
		 * Get array of DOM Elements by their ids.
		 *
		 * @method get
		 * @param {String} id Identifier of the DOM Element
		 * @return {Array}
		 */
		getAll: function get(ids) {
			var els = [],
				el;

			if (u.Basic.typeOf(ids) !== 'array') {
				ids = [ids];
			}

			var i = ids.length;
			while (i--) {
				el = u.Dom.get(ids[i]);
				if (el) {
					els.push(el);
				}
			}

			return els.length ? els : null;
		},

		/**
		Get DOM element by id

		@method get
		@param {String} id Identifier of the DOM Element
		@return {Node}
		*/
		get: u.Dom.get,

		/**
		 * Executes the callback function for each item in array/object. If you return false in the
		 * callback it will break the loop.
		 *
		 * @method each
		 * @static
		 * @param {Object} obj Object to iterate.
		 * @param {function} callback Callback function to execute for each item.
		 */
		each: u.Basic.each,

		/**
		 * Returns the absolute x, y position of an Element. The position will be returned in a object with x, y fields.
		 *
		 * @method getPos
		 * @static
		 * @param {Element} node HTML element or element id to get x, y position from.
		 * @param {Element} root Optional root element to stop calculations at.
		 * @return {object} Absolute position of the specified element object with x, y fields.
		 */
		getPos: u.Dom.getPos,

		/**
		 * Returns the size of the specified node in pixels.
		 *
		 * @method getSize
		 * @static
		 * @param {Node} node Node to get the size of.
		 * @return {Object} Object with a w and h property.
		 */
		getSize: u.Dom.getSize,

		/**
		 * Encodes the specified string.
		 *
		 * @method xmlEncode
		 * @static
		 * @param {String} s String to encode.
		 * @return {String} Encoded string.
		 */
		xmlEncode: function(str) {
			var xmlEncodeChars = {
					'<': 'lt',
					'>': 'gt',
					'&': 'amp',
					'"': 'quot',
					'\'': '#39'
				},
				xmlEncodeRegExp = /[<>&\"\']/g;

			return str ? ('' + str).replace(xmlEncodeRegExp, function(chr) {
				return xmlEncodeChars[chr] ? '&' + xmlEncodeChars[chr] + ';' : chr;
			}) : str;
		},

		/**
		 * Forces anything into an array.
		 *
		 * @method toArray
		 * @static
		 * @param {Object} obj Object with length field.
		 * @return {Array} Array object containing all items.
		 */
		toArray: u.Basic.toArray,

		/**
		 * Find an element in array and return its index if present, otherwise return -1.
		 *
		 * @method inArray
		 * @static
		 * @param {mixed} needle Element to find
		 * @param {Array} array
		 * @return {Int} Index of the element, or -1 if not found
		 */
		inArray: u.Basic.inArray,

		/**
		 * Extends the language pack object with new items.
		 *
		 * @method addI18n
		 * @static
		 * @param {Object} pack Language pack items to add.
		 * @return {Object} Extended language pack object.
		 */
		addI18n: o.core.I18n.addI18n,

		/**
		 * Translates the specified string by checking for the english string in the language pack lookup.
		 *
		 * @method translate
		 * @static
		 * @param {String} str String to look for.
		 * @return {String} Translated string or the input string if it wasn't found.
		 */
		translate: o.core.I18n.translate,

		/**
		 * Pseudo sprintf implementation - simple way to replace tokens with specified values.
		 *
		 * @param {String} str String with tokens
		 * @return {String} String with replaced tokens
		 */
		sprintf: u.Basic.sprintf,

		/**
		 * Checks if object is empty.
		 *
		 * @method isEmptyObj
		 * @static
		 * @param {Object} obj Object to check.
		 * @return {Boolean}
		 */
		isEmptyObj: u.Basic.isEmptyObj,

		/**
		 * Checks if specified DOM element has specified class.
		 *
		 * @method hasClass
		 * @static
		 * @param {Object} obj DOM element like object to add handler to.
		 * @param {String} name Class name
		 */
		hasClass: u.Dom.hasClass,

		/**
		 * Adds specified className to specified DOM element.
		 *
		 * @method addClass
		 * @static
		 * @param {Object} obj DOM element like object to add handler to.
		 * @param {String} name Class name
		 */
		addClass: u.Dom.addClass,

		/**
		 * Removes specified className from specified DOM element.
		 *
		 * @method removeClass
		 * @static
		 * @param {Object} obj DOM element like object to add handler to.
		 * @param {String} name Class name
		 */
		removeClass: u.Dom.removeClass,

		/**
		 * Returns a given computed style of a DOM element.
		 *
		 * @method getStyle
		 * @static
		 * @param {Object} obj DOM element like object.
		 * @param {String} name Style you want to get from the DOM element
		 */
		getStyle: u.Dom.getStyle,

		/**
		 * Adds an event handler to the specified object and store reference to the handler
		 * in objects internal Plupload registry (@see removeEvent).
		 *
		 * @method addEvent
		 * @static
		 * @param {Object} obj DOM element like object to add handler to.
		 * @param {String} name Name to add event listener to.
		 * @param {Function} callback Function to call when event occurs.
		 * @param {String} (optional) key that might be used to add specifity to the event record.
		 */
		addEvent: u.Events.addEvent,

		/**
		 * Remove event handler from the specified object. If third argument (callback)
		 * is not specified remove all events with the specified name.
		 *
		 * @method removeEvent
		 * @static
		 * @param {Object} obj DOM element to remove event listener(s) from.
		 * @param {String} name Name of event listener to remove.
		 * @param {Function|String} (optional) might be a callback or unique key to match.
		 */
		removeEvent: u.Events.removeEvent,

		/**
		 * Remove all kind of events from the specified object
		 *
		 * @method removeAllEvents
		 * @static
		 * @param {Object} obj DOM element to remove event listeners from.
		 * @param {String} (optional) unique key to match, when removing events.
		 */
		removeAllEvents: u.Events.removeAllEvents,

		/**
		 * Cleans the specified name from national characters (diacritics). The result will be a name with only a-z, 0-9 and _.
		 *
		 * @method cleanName
		 * @static
		 * @param {String} s String to clean up.
		 * @return {String} Cleaned string.
		 */
		cleanName: function(name) {
			var i, lookup;

			// Replace diacritics
			lookup = [
				/[\300-\306]/g, 'A', /[\340-\346]/g, 'a',
				/\307/g, 'C', /\347/g, 'c',
				/[\310-\313]/g, 'E', /[\350-\353]/g, 'e',
				/[\314-\317]/g, 'I', /[\354-\357]/g, 'i',
				/\321/g, 'N', /\361/g, 'n',
				/[\322-\330]/g, 'O', /[\362-\370]/g, 'o',
				/[\331-\334]/g, 'U', /[\371-\374]/g, 'u'
			];

			for (i = 0; i < lookup.length; i += 2) {
				name = name.replace(lookup[i], lookup[i + 1]);
			}

			// Replace whitespace
			name = name.replace(/\s+/g, '_');

			// Remove anything else
			name = name.replace(/[^a-z0-9_\-\.]+/gi, '');

			return name;
		},

		/**
		 * Builds a full url out of a base URL and an object with items to append as query string items.
		 *
		 * @method buildUrl
		 * @static
		 * @param {String} url Base URL to append query string items to.
		 * @param {Object} items Name/value object to serialize as a querystring.
		 * @return {String} String with url + serialized query string items.
		 */
		buildUrl: function(url, items) {
			var query = '';

			u.Basic.each(items, function(value, name) {
				query += (query ? '&' : '') + encodeURIComponent(name) + '=' + encodeURIComponent(value);
			});

			if (query) {
				url += (url.indexOf('?') > 0 ? '&' : '?') + query;
			}

			return url;
		},

		/**
		 * Formats the specified number as a size string for example 1024 becomes 1 KB.
		 *
		 * @method formatSize
		 * @static
		 * @param {Number} size Size to format as string.
		 * @return {String} Formatted size string.
		 */
		formatSize: function(size) {
			var self = this;

			function round(num, precision) {
				return Math.round(num * Math.pow(10, precision)) / Math.pow(10, precision);
			}

			size = parseInt(size, 10);
			if (isNaN(size)) {
				return self.translate('N/A');
			}

			var boundary = Math.pow(1024, 4);

			// TB
			if (size > boundary) {
				return round(size / boundary, 1) + " " + self.translate('tb');
			}

			// GB
			if (size > (boundary /= 1024)) {
				return round(size / boundary, 1) + " " + self.translate('gb');
			}

			// MB
			if (size > (boundary /= 1024)) {
				return round(size / boundary, 1) + " " + self.translate('mb');
			}

			// KB
			if (size > 1024) {
				return Math.round(size / 1024) + " " + self.translate('kb');
			}

			return size + " " + self.translate('b');
		},

		/**
		 * @private
		 */
		mimes2extList: moxie.core.utils.Mime.mimes2extList,

		/**
		Resolve url - among other things will turn relative url to absolute

		@method resolveUrl
		@static
		@param {String|Object} url Either absolute or relative, or a result of parseUrl call
		@return {String} Resolved, absolute url
		*/
		resolveUrl: u.Url.resolveUrl,

		/**
		 * Parses the specified size string into a byte value. For example 10kb becomes 10240.
		 *
		 * @method parseSize
		 * @static
		 * @param {String|Number} size String to parse or number to just pass through.
		 * @return {Number} Size in bytes.
		 */
		parseSize: u.Basic.parseSizeStr,

		delay: u.Basic.delay,


		/**
		Parent object for all event dispatching components and objects

		@class plupload.EventTarget
		@private
		@constructor
		*/
		EventTarget: moxie.core.EventTarget,

		/**
		Common set of methods and properties for every runtime instance

		@class plupload.Runtime
		@private

		@param {Object} options
		@param {String} type Sanitized name of the runtime
		@param {Object} [caps] Set of capabilities that differentiate specified runtime
		@param {Object} [modeCaps] Set of capabilities that do require specific operational mode
		@param {String} [preferredMode='browser'] Preferred operational mode to choose if no required capabilities were requested
		*/
		Runtime: moxie.runtime.Runtime,

		/**
		Provides a convenient way to create cross-browser file-picker. Generates file selection dialog on click,
		converts selected files to _File_ objects, to be used in conjunction with _Image_, preloaded in memory
		with _FileReader_ or uploaded to a server through _XMLHttpRequest_.

		@class plupload.FileInput
		@private
		@constructor
		@extends EventTarget
		@uses RuntimeClient
		@param {Object|String|DOMElement} options If options is string or node, argument is considered as _browse\_button_.
			@param {String|DOMElement} options.browse_button DOM Element to turn into file picker.
			@param {Array} [options.accept] Array of mime types to accept. By default accepts all.
			@param {String} [options.file='file'] Name of the file field (not the filename).
			@param {Boolean} [options.multiple=false] Enable selection of multiple files.
			@param {Boolean} [options.directory=false] Turn file input into the folder input (cannot be both at the same time).
			@param {String|DOMElement} [options.container] DOM Element to use as a container for file-picker. Defaults to parentNode
			for _browse\_button_.
			@param {Object|String} [options.required_caps] Set of required capabilities, that chosen runtime must support.
		*/
		FileInput: moxie.file.FileInput,

		/**
		Utility for preloading o.Blob/o.File objects in memory. By design closely follows [W3C FileReader](http://www.w3.org/TR/FileAPI/#dfn-filereader)
		interface. Where possible uses native FileReader, where - not falls back to shims.

		@class plupload.FileReader
		@private
		@constructor
		@extends EventTarget
		@uses RuntimeClient
		*/
		FileReader: moxie.file.FileReader
	};

});

// Included from: src/core/Collection.js

/**
 * Collection.js
 *
 * Copyright 2017, Ephox
 * Released under AGPLv3 License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */


/**
Helper collection class - in a way a mix of object and array

@contsructor
@class plupload.core.Collection
@private
*/
define('plupload/core/Collection', [
    'plupload'
], function(Basic) {

    var Collection = function() {
        var _registry = {};
        var _length = 0;
        var _last;


        plupload.extend(this, {

            count: function() {
                return _length;
            },

            hasKey: function(key) {
                return _registry.hasOwnProperty(key)
            },


            get: function(key) {
                return _registry[key];
            },


            first: function() {
                for (var key in _registry) {
                    return _registry[key];
                }
            },


            last: function() {
                return _last;
            },


            toObject: function() {
                return _registry;
            },


            add: function(key, obj) {
                var self = this;

                if (typeof(key) === 'object' && !obj) {
                    return plupload.each(key, function(obj, key) {
                        self.add(key, obj);
                    });
                }

                if (_registry.hasOwnProperty(key)) {
                    return self.update.apply(self, arguments);
                }

                _registry[key] = _last = obj;
                _length++;
            },


            remove: function(key) {
                if (this.hasKey(key)) {
                    var last = _registry[key];

                    delete _registry[key];
                    _length--;

                    // renew ref to the last added item if necessary
                    if (_last === last) {
                        _last = findLast();
                    }
                }
            },


            extract: function(key) {
                var item = this.get(key);
                this.remove(key);
                return item;
            },


            shift: function() {
                var self = this,
                    first, key;

                for (key in _registry) {
                    first = _registry[key];
                    self.remove(key);
                    return first;
                }
            },


            update: function(key, obj) {
                _registry[key] = obj;
            },


            each: function(cb) {
                plupload.each(_registry, cb);
            },


            combineWith: function() {
                var newCol = new Collection();

                newCol.add(_registry);

                plupload.each(arguments, function(col) {
                    if (col instanceof Collection) {
                        newCol.add(col.toObject());
                    }
                });
                return newCol;
            },


            clear: function() {
                _registry = {};
                _last = null;
                _length = 0;
            }
        });


        function findLast() {
            var key;
            for (key in _registry) {}
            return _registry[key];
        }

    };

    return Collection;
});

// Included from: src/core/ArrCollection.js

/**
 * ArrCollection.js
 *
 * Copyright 2017, Ephox
 * Released under AGPLv3 License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */


/**
@contsructor
@class plupload.core.ArrCollection
@private
*/
define('plupload/core/ArrCollection', [
    'plupload'
], function(plupload) {

    var ArrCollection = function() {
        var _registry = [];

        plupload.extend(this, {

            count: function() {
                return _registry.length;
            },

            hasKey: function(key) {
                return this.getIdx(key) > -1;
            },


            get: function(key) {
                var idx = this.getIdx(key);
                return idx > -1 ? _registry[idx] : null;
            },

            getIdx: function(key) {
                for (var i = 0, length = _registry.length; i < length; i++) {
                    if (_registry[i].uid === key) {
                        return i;
                    }
                }
                return -1;
            },

            getByIdx: function(idx) {
                return _registry[idx]
            },

            first: function() {
                return _registry[0];
            },

            last: function() {
                return _registry[_registry.length - 1];
            },

            add: function(obj) {
                obj = arguments[1] || obj; // make it compatible with Collection.add()

                var idx = this.getIdx(obj.uid);
                if (idx > -1) {
                    _registry[idx] = obj;
                    return idx;
                }

                _registry.push(obj);
                return _registry.length - 1;
            },

            remove: function(key) {
                return !!this.extract(key);
            },

            splice: function(start, length) {
                start = plupload.typeOf(start) === 'undefinded' ? 0 : Math.max(start, 0);
                length = plupload.typeOf(length) !== 'undefinded' && start + length < _registry.length ? length : _registry.length - start;

                return _registry.splice(start, length);
            },

            extract: function(key) {
                var idx = this.getIdx(key);
                if (idx > -1) {
                    return _registry.splice(idx, 1);
                }
                return null;
            },

            shift: function() {
                return _registry.shift();
            },

            update: function(key, obj) {
                var idx = this.getIdx(key);
                if (idx > -1) {
                    _registry[idx] = obj;
                    return true;
                }
                return false;
            },

            each: function(cb) {
                plupload.each(_registry, cb);
            },

            combineWith: function() {
                return Array.prototype.concat.apply(this.toArray(), arguments);
            },

            sort: function(cb) {
                _registry.sort(cb || function(a, b) {
                    return a.priority - b.priority;
                });
            },

            clear: function() {
                _registry = [];
            },

            toObject: function() {
                var obj = {};
                for (var i = 0, length = _registry.length; i < length; i++) {
                    obj[_registry[i].uid] = _registry[i];
                }
                return obj;
            },

            toArray: function() {
                return Array.prototype.slice.call(_registry);
            }
        });
    };

    return ArrCollection;
});

// Included from: src/core/Optionable.js

/**
 * Optionable.js
 *
 * Copyright 2017, Ephox
 * Released under AGPLv3 License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */


/**
@contsructor
@class plupload.core.Optionable
@private
@since 3.0
*/
define('plupload/core/Optionable', [
    'plupload'
], function(plupload) {
    var EventTarget = moxie.core.EventTarget;

    var dispatches = [
        /**
         * Dispatched when option is being changed.
         *
         * @event OptionChanged
         * @param {Object} event
         * @param {String} name Name of the option being changed
         * @param {Mixed} value
         * @param {Mixed} oldValue
         */
        'OptionChanged'
    ];

    return (function(Parent) {

        /**
         * @class Optionable
         * @constructor
         * @extends EventTarget
         */
        function Optionable() {
            Parent.apply(this, arguments);

            this._options = {};
        }

        plupload.inherit(Optionable, Parent);

        plupload.extend(Optionable.prototype, {
            /**
             * Set the value for the specified option(s).
             *
             * @method setOption
             * @since 2.1
             * @param {String|Object} option Name of the option to change or the set of key/value pairs
             * @param {Mixed} [value] Value for the option (is ignored, if first argument is object)
             * @param {Boolean} [mustBeDefined] if truthy, any option that is not in defaults will be ignored
             */
            setOption: function(option, value, mustBeDefined) {
                var self = this;
                var oldValue;

                if (typeof(option) === 'object') {
                    mustBeDefined = value;
                    plupload.each(option, function(value, option) {
                        self.setOption(option, value, mustBeDefined);
                    });
                    return;
                }

                if (mustBeDefined && !self._options.hasOwnProperty(option)) {
                    return;
                }

                oldValue = plupload.clone(self._options[option]);

                //! basically if an option is of type object extend it rather than replace
                if (plupload.typeOf(value) === 'object' && plupload.typeOf(self._options[option]) === 'object') {
                     // having some options as objects was a bad idea, prefixes is the way
                    plupload.extend(self._options[option], value);
                } else {
                    self._options[option] = value;
                }

                self.trigger('OptionChanged', option, value, oldValue);
            },

            /**
             * Get the value for the specified option or the whole configuration, if not specified.
             *
             * @method getOption
             * @since 2.1
             * @param {String} [option] Name of the option to get
             * @return {Mixed} Value for the option or the whole set
             */
            getOption: function(option) {
                if (!option) {
                    return this._options;
                }

                var value = this._options[option];
                if (plupload.inArray(plupload.typeOf(value), ['array', 'object']) > -1) {
                    return plupload.extendImmutable({}, value);
                } else {
                    return value;
                }
            },


            /**
             * Set many options as once.
             *
             * @method setOptions
             * @param {Object} options
             * @param {Boolean} [mustBeDefined] if truthy, any option that is not in defaults will be ignored
             */
            setOptions: function(options, mustBeDefined) {
                if (typeof(options) !== 'object') {
                    return;
                }
                this.setOption(options, mustBeDefined);
            },


            /**
            Gets all options.

            @method getOptions
            @return {Object}
            */
            getOptions: function() {
                return this.getOption();
            }
        });

        return Optionable;

    }(EventTarget));

});

// Included from: src/core/Queueable.js

/**
 * Queueable.js
 *
 * Copyright 2017, Ephox
 * Released under AGPLv3 License.se.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */


/**
Every queue item must have properties, implement methods and fire events defined in this class

@contsructor
@class plupload.core.Queueable
@private
@decorator
@extends EventTarget
*/
define('plupload/core/Queueable', [
    'plupload',
    'plupload/core/Optionable'
], function(plupload, Optionable) {

    var dispatches = [
        /**
         * Dispatched every time the state of queue changes
         *
         * @event statechanged
         * @param {Object} event
         * @param {Number} state New state
         * @param {Number} prevState Previous state
         */
        'statechanged',


        /**
         * Dispatched when the item is put on pending list
         *
         * @event queued
         * @param {Object} event
         */
        'queued',


        /**
         * Dispatched as soon as activity starts
         *
         * @event started
         * @param {Object} event
         */
        'started',


        'paused',


        'resumed',


        'stopped',


        /**
         * Dispatched as the activity progresses
         *
         * @event
         * @param {Object} event
         *      @param {Number} event.percent
         *      @param {Number} [event.processed]
         *      @param {Number} [event.total]
         */
        'progress',


        'failed',


        'done',


        'processed',

        'destroy'
    ];


    return (function(Parent) {

        function Queueable() {
            Parent.apply(this, arguments);

            /**
            Unique identifier
            @property uid
            @type {String}
            */
            this.uid = plupload.guid();

            this.state = Queueable.IDLE;

            this.processed = 0;

            this.total = 0;

            this.percent = 0;

            this.retries = 0;

            /**
             * Can be 0-Infinity - item with higher priority will have well... higher priority
             * @property [priority=0]
             * @type {Number}
             */
            this.priority = 0;

            this.startedTimestamp = 0;

            /**
             * Set when item becomes Queueable.DONE or Queueable.FAILED.
             * Used to calculate proper processedPerSec for the queue stats.
             * @property processedTimestamp
             * @type {Number}
             */
            this.processedTimestamp = 0;
			var MXI_DEBUG = false;

            if (MXI_DEBUG) {
                this.bind('StateChanged', function(e, state, oldState) {
                    var self = this;

                    var stateToString = function(code) {
                        switch (code) {
                            case Queueable.IDLE:
                                return 'IDLE';

                            case Queueable.PROCESSING:
                                return 'PROCESSING';

                            case Queueable.PAUSED:
                                return 'PAUSED';

                            case Queueable.RESUMED:
                                return 'RESUMED';

                            case Queueable.DONE:
                                return 'DONE';

                            case Queueable.FAILED:
                                return 'FAILED';

                            case Queueable.DESTROYED:
                                return 'DESTROYED';
                        }
                    };

                    var indent = function() {
                        switch (self.ctorName) {
                            case 'File':
                                return "\t".repeat(2);

                            case 'QueueUpload':
                            case 'QueueResize':
                                return "\t";

                            case 'FileUploader':
                                return "\t".repeat(3);

                            case 'ChunkUploader':
                                return "\t".repeat(4);

                            default:
                                return "\t";
                        }
                    };

                    plupload.ua.log("StateChanged:" + indent() + self.ctorName + '::' + self.uid + ' (' + stateToString(oldState) + ' to ' + stateToString(state) + ')');
                }, 999);
            }
        }

        Queueable.IDLE = 1;
        Queueable.PROCESSING = 2;
        Queueable.PAUSED = 6;
        Queueable.RESUMED = 7;
        Queueable.DONE = 5;
        Queueable.FAILED = 4;
        Queueable.DESTROYED = 8;

        plupload.inherit(Queueable, Parent);

        plupload.extend(Queueable.prototype, {

            start: function() {
                var prevState = this.state;

                if (this.state === Queueable.PROCESSING) {
                    return false;
                }

                if (!this.startedTimestamp) {
                    this.startedTimestamp = +new Date();
                }

                this.state = Queueable.PROCESSING;
                this.trigger('statechanged', this.state, prevState);
                this.trigger('started');

                return true;
            },


            pause: function() {
                var prevState = this.state;

                if (plupload.inArray(this.state, [Queueable.IDLE, Queueable.RESUMED, Queueable.PROCESSING]) === -1) {
                    return false;
                }

                this.processed = this.percent = 0; // by default reset all progress
                this.loaded = this.processed; // for backward compatibility

                this.state = Queueable.PAUSED;
                this.trigger('statechanged', this.state, prevState);
                this.trigger('paused');
                return true;
            },


            resume: function() {
                var prevState = this.state;

                if (this.state !== Queueable.PAUSED && this.state !== Queueable.RESUMED) {
                    return false;
                }

                this.state = Queueable.RESUMED;
                this.trigger('statechanged', this.state, prevState);
                this.trigger('resumed');
                return true;
            },


            stop: function() {
                var prevState = this.state;

                if (this.state === Queueable.IDLE) {
                    return false;
                }

                this.processed = this.percent = 0;
                this.loaded = this.processed; // for backward compatibility

                this.startedTimestamp = 0;

                this.state = Queueable.IDLE;
                this.trigger('statechanged', this.state, prevState);
                this.trigger('stopped');
                return true;
            },


            done: function(result) {
                var prevState = this.state;

                if (this.state === Queueable.DONE) {
                    return false;
                }

                this.processed = this.total;
                this.loaded = this.processed; // for backward compatibility
                this.percent = 100;

                this.processedTimestamp = +new Date();

                this.state = Queueable.DONE;
                this.trigger('statechanged', this.state, prevState);
                this.trigger('done', result);
                this.trigger('processed');
                return true;
            },


            failed: function(result) {
                var prevState = this.state;

                if (this.state === Queueable.FAILED) {
                    return false;
                }

                this.processed = this.percent = 0; // reset the progress
                this.loaded = this.processed; // for backward compatibility

                this.processedTimestamp = +new Date();

                this.state = Queueable.FAILED;
                this.trigger('statechanged', this.state, prevState);
                this.trigger('failed', result);
                this.trigger('processed');
                return true;
            },


            progress: function(processed, total) {
                if (total) {
                    this.total = total; // is this even required?
                }

                this.processed = Math.min(processed, this.total);
                this.loaded = this.processed; // for backward compatibility
                this.percent = Math.ceil(this.processed / this.total * 100);

                this.trigger({
                    type: 'progress',
                    loaded: this.processed,
                    total: this.total
                });
            },


            destroy: function() {
                var prevState = this.state;

                if (this.state === Queueable.DESTROYED) {
                    return false; // already destroyed
                }

                this.state = Queueable.DESTROYED;
                this.trigger('statechanged', this.state, prevState);
                this.trigger('destroy');
                this.unbindAll();
                return true;
            }

        });

        return Queueable;

    }(Optionable));
});

// Included from: src/core/Stats.js

/**
@class plupload.core.Stats
@constructor
@private
*/
define('plupload/core/Stats', [], function() {

	return function() {
		var self = this;

		/**
		 * Total queue file size.
		 *
		 * @property size
		 * @deprecated use total
		 * @type Number
		 */
		self.size = 0;

		/**
		 * Total size of the queue in units.
		 *
		 * @property total
		 * @since 3.0
		 * @type Number
		 */
		self.total = 0;

		/**
		 * Total bytes uploaded.
		 *
		 * @property loaded
		 * @type Number
		 */
		self.loaded = 0;


		/**
		 * Number of files uploaded successfully.
		 *
		 * @property uploaded
		 * @deprecated use done
		 * @type Number
		 */
		self.uploaded = 0;

		/**
		 * Number of items processed successfully.
		 *
		 * @property done
		 * @since 3.0
		 * @type Number
		 */
		self.done = 0;

		/**
		 * Number of failed items.
		 *
		 * @property failed
		 * @type Number
		 */
		self.failed = 0;

		/**
		 * Number of items yet to be processed.
		 *
		 * @property queued
		 * @type Number
		 */
		self.queued = 0;

		/**
		 * Number of items currently paused.
		 *
		 * @property paused
		 * @type Number
		 */
		self.paused = 0;

		/**
		 * Number of items being processed.
		 *
		 * @property processing
		 * @type Number
		 */
		self.processing = 0;


		/**
		 * Number of items being paused.
		 *
		 * @property paused
		 * @type Number
		 */
		self.paused = 0;

		/**
		 * Percent of processed units.
		 *
		 * @property percent
		 * @type Number
		 */
		self.percent = 0;

		/**
		 * Bytes processed per second.
		 *
		 * @property bytesPerSec
		 * @deprecated use processedPerSec
		 * @type Number
		 */
		self.bytesPerSec = 0;

		/**
		 * Units processed per second.
		 *
		 * @property processedPerSec
		 * @since 3.0
		 * @type Number
		 */
		self.processedPerSec = 0;

		/**
		 * Resets the progress to its initial values.
		 *
		 * @method reset
		 */
		self.reset = function() {
			self.size = // deprecated
			self.total =
			self.loaded = // deprecated
			self.processed =
			self.uploaded = // deprecated
			self.done =
			self.failed =
			self.queued =
			self.processing =
			self.paused =
			self.percent =
			self.bytesPerSec = // deprecated
			self.processedPerSec = 0;
		};
	};
});

// Included from: src/core/Queue.js

/**
 * Queue.js
 *
 * Copyright 2017, Ephox
 * Released under AGPLv3 License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */


/**
@contsructor
@class plupload.core.Queue
@private
*/
define('plupload/core/Queue', [
    'plupload',
    'plupload/core/ArrCollection',
    'plupload/core/Queueable',
    'plupload/core/Stats'
], function(plupload, ArrCollection, Queueable, Stats) {

    var dispatches = [
        /**
         * Dispatched as soon as activity starts
         *
         * @event started
         * @param {Object} event
         */
        'Started',


        /**
         * Dispatched as activity progresses
         *
         * @event progress
         * @param {Object} event
         * @param {Number} processed
         * @param {Number} total
         * @param {plupload.core.Stats} stats
         */
        'Progress',

        /**
         * Dispatched when activity is paused
         *
         * @event paused
         * @param {Object} event
         */
        'Paused',

        /**
         * Dispatched when there's no more items in processing
         *
         * @event done
         * @param {Object} event
         */
        'Done',

        /**
         * Dispatched as soon as activity ends
         *
         * @event stopped
         * @param {Object} event
         */
        'Stopped',

        /**
         * Dispatched when queue is destroyed
         *
         * @event destroy
         * @param {Object} event
         */
        'Destroy'
    ];

    /**
     * @class Queue
     * @constructor
     * @extends EventTarget
     */
    return (function(Parent) {
        plupload.inherit(Queue, Parent);


        function Queue(options) {
            Parent.apply(this, arguments);

            /**
            @property _queue
            @type {Collection}
            @private
            */
            this._queue = new ArrCollection();


            /**
            @property stats
            @type {Stats}
            @readOnly
            */
            this.stats = new Stats();


            this._options = plupload.extend({}, this._options, {
                max_slots: 1,
                max_retries: 0,
                auto_start: false,
                finish_active: false
            }, options);
        }

        plupload.extend(Queue.prototype, {

            /**
             * Returns number of items in the queue
             *
             * @method count
             * @returns {Number}
             */
            count: function() {
                return this._queue.count();
            },

            /**
             * Start the queue
             *
             * @method start
             */
            start: function() {
                if (!Queue.parent.start.call(this)) {
                    return false;
                }
                return processNext.call(this);
            },


            pause: function() {
                if (!Queue.parent.pause.call(this)) {
                    return false;
                }

                this.forEachItem(function(item) {
                    item.pause();
                });
            },

            /**
             * Stop the queue. If `finish_active=true` the queue will wait until active items are done, before
             * stopping.
             *
             * @method stop
             */
            stop: function() {
                if (!Queue.parent.stop.call(this) || this.getOption('finish_active')) {
                    return false;
                }

                if (this.isActive()) {
                    this.forEachItem(function(item) {
                        item.stop();
                    });
                }
            },


            forEachItem: function(cb) {
                this._queue.each(cb);
            },


            getItem: function(uid) {
                return this._queue.get(uid);
            },


            /**
             * Add instance of Queueable to the queue. If `auto_start=true` queue will start as well.
             *
             * @method addItem
             * @param {Queueable} item
             */
            addItem: function(item) {
                var self = this;

                item.bind('Started', function() {
                    if (self.calcStats()) {
                        plupload.delay.call(self, processNext);
                    }
                });

                item.bind('Resumed',function() {
                    self.start();
                });

                item.bind('Paused', function() {
                    if (self.calcStats()) {
                        plupload.delay.call(self, function() {
                            if (!processNext.call(self) && !self.stats.processing) {
                                self.pause();
                            }
                        });
                    }
                });

                item.bind('Processed Stopped', function() {
                    if (self.calcStats()) {
                        plupload.delay.call(self, function() {
                            if (!processNext.call(self) && !this.isStopped() && !this.isActive()) {
                                self.stop();
                            }
                        });
                    }
                });

                item.bind('Progress', function() {
                    if (self.calcStats()) {
                        self.trigger('Progress', self.stats.processed, self.stats.total, self.stats);
                    }
                });

                item.bind('Failed', function() {
                    if (self.getOption('max_retries') && this.retries < self.getOption('max_retries')) {
                        this.stop();
                        this.retries++;
                    }
                });

                this._queue.add(item.uid, item);
                this.calcStats();
                item.trigger('Queued');

                if (self.getOption('auto_start') || self.state === Queueable.PAUSED) {
                    plupload.delay.call(this, this.start);
                }
            },


            /**
             * Extracts item from the queue by its uid and returns it.
             *
             * @method extractItem
             * @param {String} uid
             * @return {Queueable} Item that was removed
             */
            extractItem: function(uid) {
                var item = this._queue.get(uid);
                if (item) {
                    this.stopItem(item.uid);
                    this._queue.remove(uid);
                    this.calcStats();
                }
                return item;
            },

            /**
             * Removes item from the queue and destroys it
             *
             * @method removeItem
             * @param {String} uid
             * @returns {Boolean} Result of the operation
             */
            removeItem: function(uid) {
                var item = this.extractItem(uid);
                if (item) {
                    item.destroy();
                    return true;
                }
                return false;
            },


            stopItem: function(uid) {
                var item = this._queue.get(uid);
                if (item) {
                    return item.stop();
                } else {
                    return false;
                }
            },


            pauseItem: function(uid) {
                var item = this._queue.get(uid);
                if (item) {
                    return item.pause();
                } else {
                    return false;
                }
            },


            resumeItem: function(uid) {
                var item = this._queue.get(uid);
                if (item) {
                    plupload.delay.call(this, function() {
                        this.start(); // start() will know if it needs to restart the queue
                    });
                    return item.resume();
                } else {
                    return false;
                }
            },


            splice: function(start, length) {
                return this._queue.splice(start, length);
            },


            isActive: function() {
                return this.stats && (this.stats.processing || this.stats.paused);
            },

            isStopped: function() {
                return this.state === Queueable.IDLE || this.state === Queueable.DESTROYED;
            },


            countSpareSlots: function() {
                return Math.max(this.getOption('max_slots') - this.stats.processing, 0);
            },


            toArray: function() {
                return this._queue.toArray();
            },


            clear: function() {
                var self = this;

                if (self.state !== Queueable.IDLE) {
                    // stop the active queue first
                    self.bindOnce('Stopped', function() {
                        self.clear();
                    });
                    return self.stop();
                } else {
                    self._queue.clear();
                    self.stats.reset();
                }
            },


            calcStats: function() {
                var self = this;
                var stats = self.stats;
                var processed = 0;
                var processedDuringThisSession = 0;

                if (!stats) {
                    return false; // maybe queue is destroyed
                }

                stats.reset();

                self.forEachItem(function(item) {
                    switch (item.state) {
                        case Queueable.DONE:
                            stats.done++;
                            stats.uploaded = stats.done; // for backward compatibility
                            break;

                        case Queueable.FAILED:
                            stats.failed++;
                            break;

                        case Queueable.PROCESSING:
                            stats.processing++;
                            break;

                        case Queueable.PAUSED:
                            stats.paused++;
                            break;

                        default:
                            stats.queued++;
                    }

                    processed += item.processed;

                    if (!item.processedTimestamp || item.processedTimestamp > self.startedTimestamp) {
                        processedDuringThisSession += processed;
                    }

                    stats.processedPerSec = Math.ceil(processedDuringThisSession / ((+new Date() - self.startedTimestamp || 1) / 1000.0));

                    stats.processed = processed;
                    stats.total += item.total;
                    if (stats.total) {
                        stats.percent = Math.ceil(stats.processed / stats.total * 100);
                    }
                });

                // enable properties inherited from Queueable

                /* TODO: this is good but it currently conflicts with deprecated total property in Uploader
                self.processed = stats.processed;
                self.total = stats.total;
                */
                self.percent = stats.percent;

                // for backward compatibility
                stats.loaded = stats.processed;
                stats.size = stats.total;
                stats.bytesPerSec = stats.processedPerSec;

                return true;
            },


            destroy: function() {
                var self = this;

                if (self.state === Queueable.DESTROYED) {
                    return false; // already destroyed
                }

                if (self.state !== Queueable.IDLE) {
                    // stop the active queue first
                    self.bindOnce('Stopped', function() {
                        plupload.delay.call(self, self.destroy);
                    });
                    return self.stop();
                } else {
                    self.clear();
                    Queue.parent.destroy.call(this);
                    self._queue = self.stats = null;
                }
                return true;
            }
        });


        /**
         * Returns another Queueable.IDLE or Queueable.RESUMED item, or null.
         */
        function getNextIdleItem() {
            var nextItem;
            this.forEachItem(function(item) {
                if (item.state === Queueable.IDLE || item.state === Queueable.RESUMED) {
                    nextItem = item;
                    return false;
                }
            });
            return nextItem ? nextItem : null;
        }


        function processNext() {
            var item;

            if (this.state !== Queueable.PROCESSING && this.state !== Queueable.PAUSED) {
                return false;
            }

            if (this.stats.processing < this.getOption('max_slots')) {
                item = getNextIdleItem.call(this);
                if (item) {
                    if (item.trigger('beforestart')) {
                        item.setOptions(this.getOptions());
                        return item.start();
                    } else {
                        item.pause();
                        // we need to call it sync, otherwise another thread may pick up the same file, while it is processed in beforestart handler
                        processNext.call(this);
                    }
                }
            }
            return false;
        }

        return Queue;

    }(Queueable));
});

// Included from: src/QueueUpload.js

/**
 * QueueUpload.js
 *
 * Copyright 2017, Ephox
 * Released under AGPLv3 License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */


/**
 @class plupload.QueueUpload
 @extends plupload.core.Queue
 @constructor
 @private
 @final
 @since 3.0
 @param {Object} options
 */
define('plupload/QueueUpload', [
    'plupload',
    'plupload/core/Queue'
], function(plupload, Queue) {

    return (function(Parent) {
        plupload.inherit(QueueUpload, Parent);

        function QueueUpload(options) {

            Queue.call(this, {
                max_slots: 1,
                max_retries: 0,
                auto_start: false,
                finish_active: false,
                url: false,
                chunk_size: 0,
                multipart: true,
                http_method: 'POST',
                params: {},
                headers: false,
                file_data_name: 'file',
                send_file_name: true,
                stop_on_fail: true
            });

            this.setOption = function(option, value) {
                if (typeof(option) !== 'object') {
                    if (option == 'max_upload_slots') {
                        option = 'max_slots';
                    }
                }
                QueueUpload.prototype.setOption.call(this, option, value, true);
            };

            this.setOptions(options);
        }

        return QueueUpload;
    }(Queue));
});

// Included from: src/QueueResize.js

/**
 * QueueResize.js
 *
 * Copyright 2017, Ephox
 * Released under AGPLv3 License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */


/**
 @class plupload.QueueResize
 @extends plupload.core.Queue
 @constructor
 @private
 @final
 @since 3.0
 @param {Object} options
*/
define('plupload/QueueResize', [
    'plupload',
    'plupload/core/Queue'
], function(plupload, Queue) {

    return (function(Parent) {
        plupload.inherit(QueueResize, Parent);

        function QueueResize(options) {

            Queue.call(this, {
                max_slots: 1,
                max_retries: 0,
                auto_start: false,
                finish_active: false,
                resize: {}
            });

            this.setOption = function(option, value) {
                if (typeof(option) !== 'object') {
                    if (option == 'max_resize_slots') {
                        option = 'max_slots';
                    }
                }
                QueueResize.prototype.setOption.call(this, option, value, true);
            };


            this.setOptions(options);
        }


        return QueueResize;
    }(Queue));
});

// Included from: src/ChunkUploader.js

/**
 * ChunkUploader.js
 *
 * Copyright 2017, Ephox
 * Released under AGPLv3 License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

/**
 * @class plupload.ChunkUploader
 * @extends plupload.core.Queueable
 * @constructor
 * @private
 * @final
 * @constructor
 */
define('plupload/ChunkUploader', [
    'plupload',
    'plupload/core/Collection',
    'plupload/core/Queueable'
], function(plupload, Collection, Queueable) {
    var XMLHttpRequest = moxie.xhr.XMLHttpRequest;
    var FormData = moxie.xhr.FormData;

    function ChunkUploader(blob) {
        var _xhr;

        Queueable.call(this);

        this._options = {
			file_data_name: 'file',
			headers: false,
			http_method: 'POST',
			multipart: true,
			params: {},
			send_file_name: true,
			url: false
		};

        plupload.extend(this, {

            start: function() {
                var self = this;
                var url;
                var formData;
                var prevState = this.state;
                var options = self._options;

                if (this.state === Queueable.PROCESSING) {
                    return false;
				}

				if (!this.startedTimestamp) {
                    this.startedTimestamp = +new Date();
                }

				this.state = Queueable.PROCESSING;
				this.trigger('statechanged', this.state, prevState);

                _xhr = new XMLHttpRequest();

                if (_xhr.upload) {
                    _xhr.upload.onprogress = function(e) {
                        self.progress(e.loaded, e.total);
                    };
                }

                _xhr.onload = function() {
                    var result = {
                        response: this.responseText,
                        status: this.status,
                        responseHeaders: this.getAllResponseHeaders()
                    };

                    if (this.status < 200 || this.status >= 400) { // assume error
                        return self.failed(result);
                    }

                    self.done(result);
                };

                _xhr.onerror = function() {
                    self.failed(); // TODO: reason here
                };

                _xhr.onloadend = function() {
                    // we do not need _xhr anymore, so destroy it
                    setTimeout(function() { // we detach to sustain reference until all handlers are done
                        if (_xhr) {
                            _xhr.destroy();
                            _xhr = null;
                        }
                    }, 1);
                };

                try {
                    url = options.multipart ? options.url : buildUrl(options.url, options.params);
                    _xhr.open(options.http_method, url, true);


                    // headers must be set after request is already opened, otherwise INVALID_STATE_ERR exception will raise
                    if (!plupload.isEmptyObj(options.headers)) {
                        plupload.each(options.headers, function(val, key) {
                            _xhr.setRequestHeader(key, val);
                        });
                    }


                    if (options.multipart) {
                        formData = new FormData();

                        if (!plupload.isEmptyObj(options.params)) {
                            plupload.each(options.params, function(val, key) {
                                formData.append(key, val);
                            });
                        }

                        formData.append(options.file_data_name, blob);

                        _xhr.send(formData);
                    } else { // if no multipart, send as binary stream
                        if (plupload.isEmptyObj(options.headers) || !_xhr.hasRequestHeader('content-type')) {
                            _xhr.setRequestHeader('content-type', 'application/octet-stream'); // binary stream header
                        }

                        _xhr.send(blob);
                    }

                    this.trigger('started');
                } catch(ex) {
                    self.failed();
                }
            },


            stop: function() {
                if (_xhr) {
                    _xhr.abort();
                    _xhr.destroy();
                    _xhr = null;
                }
                ChunkUploader.prototype.stop.call(this);
            },

            setOption: function(option, value) {
				ChunkUploader.prototype.setOption.call(this, option, value, true);
			},

			setOptions: function(options) {
				ChunkUploader.prototype.setOption.call(this, options, true);
			},

            destroy: function() {
                this.stop();
                ChunkUploader.prototype.destroy.call(this);
            }
        });


        /**
         * Builds a full url out of a base URL and an object with items to append as query string items.
         *
         * @method buildUrl
         * @private
         * @param {String} url Base URL to append query string items to.
         * @param {Object} items Name/value object to serialize as a querystring.
         * @return {String} String with url + serialized query string items.
         */
        function buildUrl(url, items) {
            var query = '';

            plupload.each(items, function(value, name) {
                query += (query ? '&' : '') + encodeURIComponent(name) + '=' + encodeURIComponent(value);
            });

            if (query) {
                url += (url.indexOf('?') > 0 ? '&' : '?') + query;
            }

            return url;
        }

    }

    plupload.inherit(ChunkUploader, Queueable);

    return ChunkUploader;
});

// Included from: src/FileUploader.js

/**
 * FileUploader.js
 *
 * Copyright 2017, Ephox
 * Released under AGPLv3 License.se.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

/**
 * @class plupload.FileUploader
 * @extends plupload.core.Queueable
 * @constructor
 * @since 3.0
 * @final
 */
define('plupload/FileUploader', [
	'plupload',
	'plupload/core/Collection',
	'plupload/core/Queueable',
	'plupload/ChunkUploader'
], function(plupload, Collection, Queueable, ChunkUploader) {


	function FileUploader(file, queue) {
		var _chunks = new Collection();
		var _totalChunks = 1;

		Queueable.call(this);

		this._options = {
			chunk_size: 0,
			params: {},
			send_file_name: true,
			stop_on_fail: true
		};

		plupload.extend(this, {
			/**
			When send_file_name is set to true, will be sent with the request as `name` param.
            Can be used on server-side to override original file name.

            @property name
			@type {String}
            */
			name: file.name,


			start: function() {
				var self = this;
				var prevState = this.state;
				var up;

				if (this.state === Queueable.PROCESSING) {
                    return false;
				}

				if (!this.startedTimestamp) {
                    this.startedTimestamp = +new Date();
                }

				this.state = Queueable.PROCESSING;
				this.trigger('statechanged', this.state, prevState);

				// send additional 'name' parameter only if required or explicitly requested
				if (self._options.send_file_name) {
					self._options.params.name = self.target_name || self.name;
				}

				if (self._options.chunk_size) {
					_totalChunks = Math.ceil(file.size / self._options.chunk_size);
					self.uploadChunk(false, true);
				} else {
					up = new ChunkUploader(file);

					up.bind('progress', function(e) {
						self.progress(e.loaded, e.total);
					});

					up.bind('done', function(e, result) {
						self.done(result);
					});

					up.bind('failed', function(e, result) {
						self.failed(result);
					});

					up.setOptions(self._options);

					queue.addItem(up);
				}

				this.trigger('started');
			},


			uploadChunk: function(seq, dontStop) {
				var self = this;
				var chunkSize = this.getOption('chunk_size');
				var up;
				var chunk = {};
				var _options;

				chunk.seq = parseInt(seq, 10) || getNextChunk();
				chunk.start = chunk.seq * chunkSize;
				chunk.end = Math.min(chunk.start + chunkSize, file.size);
				chunk.total = file.size;

				// do not proceed for weird chunks
				if (chunk.start < 0 || chunk.start >= file.size) {
					return false;
				}

				_options = plupload.extendImmutable({}, this.getOptions(), {
					params: {
						chunk: chunk.seq,
						chunks: _totalChunks
					}
				});

				up = new ChunkUploader(file.slice(chunk.start, chunk.end, file.type));

				up.bind('progress', function(e) {
					self.progress(calcProcessed() + e.loaded, file.size);
				});

				up.bind('failed', function(e, result) {
					_chunks.add(chunk.seq, plupload.extend({
						state: Queueable.FAILED
					}, chunk));

					self.trigger('chunkuploadfailed', plupload.extendImmutable({}, chunk, result));

					if (_options.stop_on_fail) {
						self.failed(result);
					}
				});

				up.bind('done', function(e, result) {
					_chunks.add(chunk.seq, plupload.extend({
						state: Queueable.DONE
					}, chunk));

					self.trigger('chunkuploaded', plupload.extendImmutable({}, chunk, result));

					if (calcProcessed() >= file.size) {
						self.progress(file.size, file.size);
						self.done(result); // obviously we are done
					} else if (dontStop) {
						plupload.delay(function() {
							self.uploadChunk(getNextChunk(), dontStop);
						});
					}
				});

				up.bind('processed', function() {
					this.destroy();
				});

				up.setOptions(_options);

				_chunks.add(chunk.seq, plupload.extend({
					state: Queueable.PROCESSING
				}, chunk));

				queue.addItem(up);

				// enqueue even more chunks if slots available
				if (dontStop && queue.countSpareSlots()) {
					self.uploadChunk(getNextChunk(), dontStop);
				}

				return true;
			},

			destroy: function() {
				FileUploader.prototype.destroy.call(this);
				_chunks.clear();
			}
		});


		function calcProcessed() {
			var processed = 0;

			_chunks.each(function(item) {
				if (item.state === Queueable.DONE) {
					processed += (item.end - item.start);
				}
			});

			return processed;
		}


		function getNextChunk() {
			var i = 0;
			while (i < _totalChunks && _chunks.hasKey(i)) {
				i++;
			}
			return i;
		}

	}


	plupload.inherit(FileUploader, Queueable);

	return FileUploader;
});

// Included from: src/ImageResizer.js

/**
 * ImageResizer.js
 *
 * Copyright 2017, Ephox
 * Released under AGPLv3 License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

/**
 @class plupload.ImageResizer
 @extends plupload.core.Queueable
 @constructor
 @private
 @final
 @since 3.0
 @param {plupload.File} fileRef
*/
define("plupload/ImageResizer", [
	'plupload',
	'plupload/core/Queueable'
], function(plupload, Queueable) {
	var mxiImage = moxie.image.Image;

	function ImageResizer(fileRef) {

		Queueable.call(this);

		this._options = {
			type: 'image/jpeg',
			quality: 90,
			crop: false,
			fit: true,
			preserveHeaders: true,
			resample: 'default',
			multipass: true
		};

		this.setOption = function(option, value) {
			if (typeof(option) !== 'object' && !this._options.hasOwnProperty(option)) {
				return;
			}
			ImageResizer.prototype.setOption.apply(this, arguments);
		};


		this.start = function(options) {
			var self = this;
			var img;

			if (options) {
				this.setOptions(options.resize);
			}

			img = new mxiImage();

			img.bind('load', function() {
				this.resize(self.getOptions());
			});

			img.bind('resize', function() {
				self.done(this.getAsBlob(self.getOption('type'), self.getOption('quality')));
				this.destroy();
			});

			img.bind('error', function() {
				self.failed();
				this.destroy();
			});

			img.load(fileRef, self.getOption('runtimeOptions'));
		};
	}

	plupload.inherit(ImageResizer, Queueable);

	// ImageResizer is only included for builds with Image manipulation support, so we add plupload.Image here manually
	plupload.Image = mxiImage;

	return ImageResizer;
});

// Included from: src/File.js

/**
 * File.js
 *
 * Copyright 2017, Ephox
 * Released under AGPLv3 License.se.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

/**
 * @class plupload.File
 * @extends plupload.core.Queueable
 * @constructor
 * @since 3.0
 * @final
 */
define('plupload/File', [
    'plupload',
    'plupload/core/Queueable',
    'plupload/FileUploader',
    'plupload/ImageResizer'
], function(plupload, Queueable, FileUploader, ImageResizer) {

    function File(file, queueUpload, queueResize) {
        Queueable.call(this);


        plupload.extend(this, {
            /**
             * For backward compatibility
             *
             * @property id
             * @type {String}
             * @deprecated
             */
            id: this.uid,


            /**
             When send_file_name is set to true, will be sent with the request as `name` param.
             Can be used on server-side to override original file name.

             @property name
             @type {String}
             */
            name: file.name,

            /**
             @property target_name
             @type {String}
             @deprecated use name
             */
            target_name: null,

            /**
             * File type, `e.g image/jpeg`
             *
             * @property type
             * @type String
             */
            type: file.type,

            /**
             * File size in bytes (may change after client-side manupilation).
             *
             * @property size
             * @type Number
             */
            size: file.size,

            /**
             * Original file size in bytes.
             *
             * @property origSize
             * @type Number
             */
            origSize: file.size,

            start: function() {
                var prevState = this.state;

                if (this.state === Queueable.PROCESSING) {
                    return false;
                }

                this.state = Queueable.PROCESSING;
                this.trigger('statechanged', this.state, prevState);
                this.trigger('started');

                if (!plupload.isEmptyObj(this._options.resize) && isImage(this.type) && runtimeCan(file, 'send_binary_string')) {
                    this.resizeAndUpload();
                } else {
                    this.upload();
                }
                return true;
            },

            /**
             * Get the file for which this File is responsible
             *
             * @method getSource
             * @returns {moxie.file.File}
             */
            getSource: function() {
                return file;
            },

            /**
             * Returns file representation of the current runtime. For HTML5 runtime
             * this is going to be native browser File object
             * (for backward compatibility)
             *
             * @method getNative
             * @deprecated
             * @returns {File|Blob|Object}
             */
            getNative: function() {
                return this.getFile().getSource();
            },


            resizeAndUpload: function() {
                var self = this;
                var opts = self.getOptions();
                var rszr = new ImageResizer(file);

                rszr.bind('progress', function(e) {
                    self.progress(e.loaded, e.total);
                });

                rszr.bind('done', function(e, file) {
                    file = file;
                    self.upload();
                });

                rszr.bind('failed', function() {
                    self.upload();
                });

                rszr.setOption('runtimeOptions', {
                    runtime_order: opts.runtimes,
                    required_caps: opts.required_features,
                    preferred_caps: opts.preferred_caps,
                    swf_url: opts.flash_swf_url,
                    xap_url: opts.silverlight_xap_url
                });

                queueResize.addItem(rszr);
            },


            upload: function() {
                var self = this;
                var up = new FileUploader(file, queueUpload);

                up.bind('paused', function() {
                    self.pause();
                });

                up.bind('resumed', function() {
                    this.start();
                });

                up.bind('started', function() {
                    self.trigger('startupload');
                });

                up.bind('progress', function(e) {
                    self.progress(e.loaded, e.total);
                });

                up.bind('done', function(e, result) {
                    self.done(result);
                });

                up.bind('failed', function(e, result) {
                    self.failed(result);
                });

                up.setOptions(self.getOptions());

                up.start();
            },



            destroy: function() {
                File.prototype.destroy.call(this);
                file = null;
            }
        });
    }


    function isImage(type) {
        return plupload.inArray(type, ['image/jpeg', 'image/png']) > -1;
    }


    function runtimeCan(blob, cap) {
        if (blob.ruid) {
            var info = plupload.Runtime.getInfo(blob.ruid);
            if (info) {
                return info.can(cap);
            }
        }
        return false;
    }


    plupload.inherit(File, Queueable);

    return File;
});

// Included from: src/Uploader.js

/**
 * Uploader.js
 *
 * Copyright 2017, Ephox
 * Released under AGPLv3 License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */


/**
@class plupload.Uploader
@extends plupload.core.Queue
@constructor
@public
@final

@param {Object} settings For detailed information about each option check documentation.
	@param {String|DOMElement} settings.browse_button id of the DOM element or DOM element itself to use as file dialog trigger.
	@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.
	@param {Boolean} [settings.send_chunk_number=true] Whether to send chunks and chunk numbers, or total and offset bytes.
	@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.
	@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.
	@param {String} [settings.file_data_name="file"] Name for the file field in Multipart formated message.
	@param {Object} [settings.filters={}] Set of file type filters.
		@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`
		@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`.
		@param {Boolean} [settings.filters.prevent_duplicates=false] Do not let duplicates into the queue. Dispatches `plupload.FILE_DUPLICATE_ERROR`.
	@param {String} [settings.flash_swf_url] URL of the Flash swf.
	@param {Object} [settings.headers] Custom headers to send with the upload. Hash of name/value pairs.
	@param {String} [settings.http_method="POST"] HTTP method to use during upload (only PUT or POST allowed).
	@param {Number} [settings.max_retries=0] How many times to retry the chunk or file, before triggering Error event.
	@param {Boolean} [settings.multipart=true] Whether to send file and additional parameters as Multipart formated message.
	@param {Boolean} [settings.multi_selection=true] Enable ability to select multiple files at once in file dialog.
	@param {Object} [settings.params] Hash of key/value pairs to send with every file upload.
	@param {String|Object} [settings.required_features] Either comma-separated list or hash of required features that chosen runtime should absolutely possess.
	@param {Object} [settings.resize] Enable resizing of images on client-side. Applies to `image/jpeg` and `image/png` only. `e.g. {width : 200, height : 200, quality : 90, crop: true}`
		 @param {Number} settings.resize.width Resulting width
		 @param {Number} [settings.resize.height=width] Resulting height (optional, if not supplied will default to width)
		 @param {String} [settings.resize.type='image/jpeg'] MIME type of the resulting image
		 @param {Number} [settings.resize.quality=90] In the case of JPEG, controls the quality of resulting image
		 @param {Boolean} [settings.resize.crop='cc'] If not falsy, image will be cropped, by default from center
		 @param {Boolean} [settings.resize.fit=true] In case of crop whether to upscale the image to fit the exact dimensions
		 @param {Boolean} [settings.resize.preserveHeaders=true] Whether to preserve meta headers (on JPEGs after resize)
		 @param {String} [settings.resize.resample='default'] Resampling algorithm to use during resize
		 @param {Boolean} [settings.resize.multipass=true] Whether to scale the image in steps (results in better quality)
 	@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.
	@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).
	@param {String} [settings.silverlight_xap_url] URL of the Silverlight xap.
	@param {Boolean} [settings.unique_names=false] If true will generate unique filenames for uploaded files.
	@param {String} settings.url URL of the server-side upload handler.
*/

/**
Fires when the current RunTime has been initialized.

@event Init
@param {plupload.Uploader} uploader Uploader instance sending the event.
 */

/**
Fires after the init event incase you need to perform actions there.

@event PostInit
@param {plupload.Uploader} uploader Uploader instance sending the event.
 */

/**
Fires when the option is changed in via uploader.setOption().

@event OptionChanged
@since 2.1
@param {plupload.Uploader} uploader Uploader instance sending the event.
@param {String} name Name of the option that was changed
@param {Mixed} value New value for the specified option
@param {Mixed} oldValue Previous value of the option
 */

/**
Fires when the silverlight/flash or other shim needs to move.

@event Refresh
@param {plupload.Uploader} uploader Uploader instance sending the event.
 */

/**
Fires when the overall state is being changed for the upload queue.

@event StateChanged
@param {plupload.Uploader} uploader Uploader instance sending the event.
 */

/**
Fires when browse_button is clicked and browse dialog shows.

@event Browse
@since 2.1.2
@param {plupload.Uploader} uploader Uploader instance sending the event.
 */

/**
Fires for every filtered file before it is added to the queue.

@event FileFiltered
@since 2.1
@param {plupload.Uploader} uploader Uploader instance sending the event.
@param {plupload.File} file Another file that has to be added to the queue.
 */

/**
Fires when the file queue is changed. In other words when files are added/removed to the files array of the uploader instance.

@event QueueChanged
@param {plupload.Uploader} uploader Uploader instance sending the event.
 */

/**
Fires after files were filtered and added to the queue.

@event FilesAdded
@param {plupload.Uploader} uploader Uploader instance sending the event.
@param {Array} files Array of FileUploader objects that were added to the queue by user.
 */

/**
Fires when file is removed from the queue.

@event FilesRemoved
@param {plupload.Uploader} uploader Uploader instance sending the event.
@param {Array} files Array of files that got removed.
 */

/**
Fires just before a file is uploaded. Can be used to cancel upload of the current file
by returning false from the handler.

@event BeforeUpload
@param {plupload.Uploader} uploader Uploader instance sending the event.
@param {plupload.File} file File to be uploaded.
 */

/**
Fires when a file is to be uploaded by the runtime.

@event UploadFile
@param {plupload.Uploader} uploader Uploader instance sending the event.
@param {plupload.File} file File to be uploaded.
 */

/**
Fires while a file is being uploaded. Use this event to update the current file upload progress.

@event UploadProgress
@param {plupload.Uploader} uploader Uploader instance sending the event.
@param {plupload.File} file File that is currently being uploaded.
 */

/**
Fires when file chunk is uploaded.

@event ChunkUploaded
@param {plupload.Uploader} uploader Uploader instance sending the event.
@param {plupload.File} file File that the chunk was uploaded for.
@param {Object} result Object with response properties.
	@param {Number} result.offset The amount of bytes the server has received so far, including this chunk.
	@param {Number} result.total The size of the file.
	@param {String} result.response The response body sent by the server.
	@param {Number} result.status The HTTP status code sent by the server.
	@param {String} result.responseHeaders All the response headers as a single string.
 */

/**
Fires when a file is successfully uploaded.

@event FileUploaded
@param {plupload.Uploader} uploader Uploader instance sending the event.
@param {plupload.File} file File that was uploaded.
@param {Object} result Object with response properties.
	@param {String} result.response The response body sent by the server.
	@param {Number} result.status The HTTP status code sent by the server.
	@param {String} result.responseHeaders All the response headers as a single string.
 */

/**
Fires when all files in a queue are uploaded

@event UploadComplete
@param {plupload.Uploader} uploader Uploader instance sending the event.
 */


/**
Fires whenever upload is aborted for some reason

@event CancelUpload
@param {plupload.Uploader} uploader Uploader instance sending the event.
 */

/**
Fires when a error occurs.

@event Error
@param {plupload.Uploader} uploader Uploader instance sending the event.
@param {Object} error Contains code, message and sometimes file and other details.
	@param {Number} error.code The plupload error code.
	@param {String} error.message Description of the error (uses i18n).
 */

/**
Fires when destroy method is called.

@event Destroy
@param {plupload.Uploader} uploader Uploader instance sending the event.
 */
define('plupload/Uploader', [
	'plupload',
	'plupload/core/Collection',
	'plupload/core/Queue',
	'plupload/QueueUpload',
	'plupload/QueueResize',
	'plupload/File'
], function(plupload, Collection, Queue, QueueUpload, QueueResize, PluploadFile) {

	var fileFilters = {};
	var undef;


	function Uploader(options) {
		var _fileInputs = [];
		var _fileDrops = [];
		var _queueUpload, _queueResize;
		var _initialized = false;
		var _disabled = false;

		var _options = normalizeOptions(plupload.extend({
			backward_compatibility: true,
			chunk_size: 0,
			file_data_name: 'file',
			filters: {
				mime_types: '*',
				prevent_duplicates: false,
				max_file_size: 0
			},
			flash_swf_url: 'js/Moxie.swf',
			// @since 2.3
			http_method: 'POST',
			// headers: false, // Plupload had a required feature with the same name, comment it to avoid confusion
			max_resize_slots: 1,
			max_retries: 0,
			max_upload_slots: 1,
			multipart: true,
			multipart_params: {}, // deprecated, use - params,
			multi_selection: true,
			// @since 3
			params: {},
			resize: false,
			runtimes: plupload.Runtime.order,
			send_chunk_number: true, // whether to send chunks and chunk numbers, instead of total and offset bytes
			send_file_name: true,
			silverlight_xap_url: 'js/Moxie.xap',

			// during normalization, these should be processed last
			required_features: false,
			preferred_caps: false
		}, options));

		Queue.call(this);


		// Add public methods
		plupload.extend(this, {

			_options: _options,

			/**
			 * Unique id for the Uploader instance.
			 *
			 * @property id
			 * @type String
			 */
			id: this.uid,

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

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

			/**
			 * Object with name/value settings.
			 *
			 * @property settings
			 * @type Object
			 * @deprecated Use `getOption()/setOption()`
			 */
			settings : _options,

			/**
			 * Current runtime name
			 *
			 * @property runtime
			 * @type String
			 * @deprecated There might be multiple runtimes per uploader
			 */
			runtime: null,

			/**
			 * Current upload queue, an array of File instances
			 *
			 * @property files
			 * @deprecated use forEachItem(callback) to cycle over the items in the queue
			 * @type Array
			 */
			files: [],

			/**
			 * Total progess information. How many files has been uploaded, total percent etc.
			 *
			 * @property total
			 * @deprecated use stats
			 */
			total: this.stats,

			/**
			 * Initializes the Uploader instance and adds internal event listeners.
			 *
			 * @method init
			 */
			init: function() {
				var self = this, preinitOpt, err;

				preinitOpt = self.getOption('preinit');
				if (typeof(preinitOpt) == "function") {
					preinitOpt(self);
				} else {
					plupload.each(preinitOpt, function(func, name) {
						self.bind(name, func);
					});
				}

				bindEventListeners.call(self);

				// Check for required options
				plupload.each(['container', 'browse_button', 'drop_element'], function(el) {
					if (self.getOption(el) === null) {
						err = {
							code: plupload.INIT_ERROR,
							message: plupload.sprintf(plupload.translate("%s specified, but cannot be found."), el)
						}
						return false;
					}
				});

				if (err) {
					return self.trigger('Error', err);
				}


				if (!self.getOption('browse_button') && !self.getOption('drop_element')) {
					return self.trigger('Error', {
						code: plupload.INIT_ERROR,
						message: plupload.translate("You must specify either browse_button or drop_element.")
					});
				}


				initControls.call(self, function(initialized) {
					var runtime;
					var initOpt = self.getOption('init');
					var queueOpts = plupload.extendImmutable({}, self.getOption(), { auto_start: true });

					if (typeof(initOpt) == "function") {
						initOpt(self);
					} else {
						plupload.each(initOpt, function(func, name) {
							self.bind(name, func);
						});
					}

					if (initialized) {
						_initialized = true;
						runtime = plupload.Runtime.getInfo(getRUID());

						_queueUpload = new QueueUpload(queueOpts);
						_queueResize = new QueueResize(queueOpts);

						self.trigger('Init', {
							ruid: runtime.uid,
							runtime: self.runtime = runtime.type
						});

						self.trigger('PostInit');
					} else {
						self.trigger('Error', {
							code: plupload.INIT_ERROR,
							message: plupload.translate('Init error.')
						});
					}
				});
			},

			/**
			 * Set the value for the specified option(s).
			 *
			 * @method setOption
			 * @since 2.1
			 * @param {String|Object} option Name of the option to change or the set of key/value pairs
			 * @param {Mixed} [value] Value for the option (is ignored, if first argument is object)
			 */
			setOption: function(option, value) {
				if (_initialized) {
					// following options cannot be changed after initialization
					if (plupload.inArray(option, [
						'container',
						'browse_button',
						'drop_element',
						'runtimes',
						'multi_selection',
						'flash_swf_url',
						'silverlight_xap_url'
					]) > -1) {
						return this.trigger('Error', {
							code: plupload.OPTION_ERROR,
							message: plupload.sprintf(plupload.translate("%s option cannot be changed.")),
							option: option
						});
					}
				}

				if (typeof(option) !== 'object') {
					value = normalizeOption(option, value, this._options);

					// queues will take in only appropriate options
					if (_queueUpload) {
						_queueUpload.setOption(option, value);
					}
					if (_queueResize) {
						_queueResize.setOption(option, value);
					}
				}

				Uploader.prototype.setOption.call(this, option, value);
			},

			/**
			 * Refreshes the upload instance by dispatching out a refresh event to all runtimes.
			 * This would for example reposition flash/silverlight shims on the page.
			 *
			 * @method refresh
			 */
			refresh: function() {
				if (_fileInputs.length) {
					plupload.each(_fileInputs, function(fileInput) {
						fileInput.trigger('Refresh');
					});
				}

				if (_fileDrops.length) {
					plupload.each(_fileDrops, function(fileDrops) {
						fileDrops.trigger('Refresh');
					});
				}

				this.trigger('Refresh');
			},

			/**
			 * Stops the upload of the queued files.
			 *
			 * @method stop
			 */
			stop: function() {
				if (Uploader.prototype.stop.call(this) && this.state != plupload.STOPPED) {
					this.trigger('CancelUpload');
				}
			},


			/**
			 * Disables/enables browse button on request.
			 *
			 * @method disableBrowse
			 * @param {Boolean} disable Whether to disable or enable (default: true)
			 */
			disableBrowse: function() {
				_disabled = arguments[0] !== undef ? arguments[0] : true;

				if (_fileInputs.length) {
					plupload.each(_fileInputs, function(fileInput) {
						fileInput.disable(_disabled);
					});
				}

				this.trigger('DisableBrowse', _disabled);
			},

			/**
			 * Returns the specified FileUploader object by id
			 *
			 * @method getFile
			 * @deprecated use getItem()
			 * @param {String} id FileUploader id to look for
			 * @return {plupload.FileUploader}
			 */
			getFile: function(id) {
				return this.getItem(id);
			},

			/**
			 * Adds file to the queue programmatically. Can be native file, instance of Plupload.File,
			 * instance of mOxie.File, input[type="file"] element, or array of these. Fires FilesAdded,
			 * if any files were added to the queue. Otherwise nothing happens.
			 *
			 * @method addFile
			 * @since 2.0
			 * @param {plupload.File|mOxie.File|File|Node|Array} file File or files to add to the queue.
			 * @param {String} [fileName] If specified, will be used as a name for the file
			 */
			addFile: function(file, fileName) {
				var self = this;
				var queue = [];
				var ruid; // spare runtime uid, for those files that do not have their own
				var filesAdded = []; // here we track the files that got filtered and are added to the queue


				function bindListeners(fileUp) {
					fileUp.bind('beforestart', function(e) {
						return self.trigger('BeforeUpload', e.target);
					});

					fileUp.bind('startupload', function() {
						self.trigger('UploadFile', this);
					});

					fileUp.bind('progress', function() {
						self.trigger('UploadProgress', this);
					});

					fileUp.bind('done', function(e, args) {
						self.trigger('FileUploaded', this, args);
					});

					fileUp.bind('failed', function(e, err) {
						self.trigger('Error', plupload.extend({
							code: plupload.HTTP_ERROR,
							message: plupload.translate('HTTP Error.'),
							file: this
						}, err));
					});
				}


				function filterFile(file, cb) {
					var queue = [];
					plupload.each(self.getOption('filters'), function(rule, name) {
						if (fileFilters[name]) {
							queue.push(function(cb) {
								fileFilters[name].call(self, rule, file, function(res) {
									cb(!res);
								});
							});
						}
					});
					plupload.inParallel(queue, cb);
				}

				/**
				 * @method resolveFile
				 * @private
				 * @param {mxiFile|mxiBlob|FileUploader|File|Blob|input[type="file"]} file
				 */
				function resolveFile(file) {
					var type = plupload.typeOf(file);
					// mxiFile (final step for other conditional branches)
					if (file instanceof moxie.file.File) {
						if (!file.ruid && !file.isDetached()) {
							if (!ruid) { // weird case
								return false;
							}
							file.ruid = ruid;
							file.connectRuntime(ruid);
						}

						queue.push(function(cb) {
							// run through the internal and user-defined filters, if any
							filterFile(file, function(err) {
								var fileUp;

								if (!err) {
									fileUp = new PluploadFile(file, _queueUpload, _queueResize);

									if (fileName) {
										fileUp.name = fileName;
									}

									bindListeners(fileUp);

									self.addItem(fileUp); // make files available for the filters by updating the main queue directly
									filesAdded.push(fileUp);
									self.trigger("FileFiltered", fileUp);
								}

								plupload.delay(cb); // do not build up recursions or eventually we might hit the limits
							});
						});
					}
					// mxiBlob
					else if (file instanceof moxie.file.Blob) {
						resolveFile(file.getSource());
						file.destroy();
					}
					// native File or blob
					else if (plupload.inArray(type, ['file', 'blob']) !== -1) {
						resolveFile(new moxie.file.File(null, file));
					}
					// input[type="file"]
					else if (type === 'node' && plupload.typeOf(file.files) === 'filelist') {
						// if we are dealing with input[type="file"]
						plupload.each(file.files, resolveFile);
					}
					// mixed array of any supported types (see above)
					else if (type === 'array') {
						fileName = null; // should never happen, but unset anyway to avoid funny situations
						plupload.each(file, resolveFile);
					}
				}

				ruid = getRUID();

				resolveFile(file);

				if (queue.length) {
					plupload.inParallel(queue, function() {
						// if any files left after filtration, trigger FilesAdded
						if (filesAdded.length) {
							self.trigger("FilesAdded", filesAdded);
						}
					});
				}
			},

			/**
			 * Removes a specific item from the queue
			 *
			 * @method removeFile
			 * @param {plupload.FileUploader|String} file
			 */
			removeFile: function(file) {
				var item = this.extractItem(typeof(file) === 'string' ? file : file.uid);
				if (item) {
					this.trigger("FilesRemoved", [item]);
					item.destroy();
				}
			},

			/**
			 * Removes part of the queue and returns removed files.
			 * Triggers FilesRemoved and consequently QueueChanged events.
			 *
			 * @method splice
			 * @param {Number} [start=0] Start index to remove from
			 * @param {Number} [length] Length of items to remove
			 */
			splice: function() {
				var i = 0;
				var shouldRestart = plupload.STARTED == this.state;

				var removed = Queue.prototype.splice.apply(this, arguments);
				if (removed.length) {
					this.trigger("FilesRemoved", removed);

					if (shouldRestart) {
						this.stop();
					}

					for (i = 0; i < removed.length; i++) {
						removed[i].destroy();
					}

					if (shouldRestart) {
						this.start();
					}
				}
			},

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

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

			// override the parent method to match Plupload-like event logic
			dispatchEvent: function(type) {
				var list, args, result;

				type = type.toLowerCase();

				list = this.hasEventListener(type);

				if (list) {
					// sort event list by priority
					list.sort(function(a, b) {
						return b.priority - a.priority;
					});

					// first argument should be current plupload.Uploader instance
					args = [].slice.call(arguments);
					args.shift();
					args.unshift(this);

					for (var i = 0; i < list.length; i++) {
						// Fire event, break chain if false is returned
						if (list[i].fn.apply(list[i].scope, args) === false) {
							return false;
						}
					}
				}
				return true;
			},

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

			@method hasEventListener
			@param {String} name Event name to check for.
			*/


			/**
			Adds an event listener by name.

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

			/**
			Removes the specified event listener.

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

			/**
			Removes all event listeners.

			@method unbindAll
			*/
		});


		// keep alive deprecated properties
		if (_options.backward_compatibility) {
			this.bind('FilesAdded FilesRemoved', function (up) {
				up.files = up.toArray();
			}, this, 999);

			this.bind('OptionChanged', function (up, name, value) {
				up.settings[name] = typeof(value) == 'object' ? plupload.extend({}, value) : value;
			}, this, 999);
		}


		function getRUID() {
			var ctrl = _fileInputs[0] || _fileDrops[0];
			if (ctrl) {
				return ctrl.getRuntime().uid;
			}
			return false;
		}


		function bindEventListeners() {
			this.bind('FilesAdded FilesRemoved', function(up) {
				up.trigger('QueueChanged');
				up.refresh();
			}, this, 999);

			this.bind('BeforeUpload', onBeforeUpload);

			this.bind('Stopped', function(up) {
				up.trigger('UploadComplete');
			});

			this.bind('Error', onError);

			this.bind('Destroy', onDestroy);
		}


		function initControls(cb) {
			var self = this;
			var initialized = 0;
			var queue = [];

			// common settings
			var options = {
				runtime_order: self.getOption('runtimes'),
				required_caps: self.getOption('required_features'),
				preferred_caps: self.getOption('preferred_caps'),
				swf_url: self.getOption('flash_swf_url'),
				xap_url: self.getOption('silverlight_xap_url')
			};

			// add runtime specific options if any
			plupload.each(self.getOption('runtimes').split(/\s*,\s*/), function(runtime) {
				if (self.getOption(runtime)) {
					options[runtime] = self.getOption(runtime);
				}
			});

			// initialize file pickers - there can be many
			if (self.getOption('browse_button')) {
				plupload.each(self.getOption('browse_button'), function(el) {
					queue.push(function(cb) {
						var fileInput = new moxie.file.FileInput(plupload.extend({}, options, {
							accept: self.getOption('filters').mime_types,
							name: self.getOption('file_data_name'),
							multiple: self.getOption('multi_selection'),
							container: self.getOption('container'),
							browse_button: el
						}));

						fileInput.onready = function() {
							var info = plupload.Runtime.getInfo(this.ruid);

							// for backward compatibility
							plupload.extend(self.features, {
								chunks: info.can('slice_blob'),
								multipart: info.can('send_multipart'),
								multi_selection: info.can('select_multiple')
							});

							initialized++;
							_fileInputs.push(this);
							cb();
						};

						fileInput.onchange = function() {
							self.addFile(this.files);
						};

						fileInput.bind('mouseenter mouseleave mousedown mouseup', function(e) {
							if (!_disabled) {
								if (self.getOption('browse_button_hover')) {
									if ('mouseenter' === e.type) {
										plupload.addClass(el, self.getOption('browse_button_hover'));
									} else if ('mouseleave' === e.type) {
										plupload.removeClass(el, self.getOption('browse_button_hover'));
									}
								}

								if (self.getOption('browse_button_active')) {
									if ('mousedown' === e.type) {
										plupload.addClass(el, self.getOption('browse_button_active'));
									} else if ('mouseup' === e.type) {
										plupload.removeClass(el, self.getOption('browse_button_active'));
									}
								}
							}
						});

						fileInput.bind('mousedown', function() {
							self.trigger('Browse');
						});

						fileInput.bind('error runtimeerror', function() {
							fileInput = null;
							cb();
						});

						fileInput.init();
					});
				});
			}

			// initialize drop zones
			if (self.getOption('drop_element')) {
				plupload.each(self.getOption('drop_element'), function(el) {
					queue.push(function(cb) {
						var fileDrop = new moxie.file.FileDrop(plupload.extend({}, options, {
							drop_zone: el
						}));

						fileDrop.onready = function() {
							var info = plupload.Runtime.getInfo(this.ruid);

							// for backward compatibility
							plupload.extend(self.features, {
								chunks: info.can('slice_blob'),
								multipart: info.can('send_multipart'),
								dragdrop: info.can('drag_and_drop')
							});

							initialized++;
							_fileDrops.push(this);
							cb();
						};

						fileDrop.ondrop = function() {
							self.addFile(this.files);
						};

						fileDrop.bind('error runtimeerror', function() {
							fileDrop = null;
							cb();
						});

						fileDrop.init();
					});
				});
			}


			plupload.inParallel(queue, function() {
				if (typeof(cb) === 'function') {
					cb(initialized);
				}
			});
		}


		// Internal event handlers
		function onBeforeUpload(up, file) {
			// Generate unique target filenames
			if (up.getOption('unique_names')) {
				var matches = file.name.match(/\.([^.]+)$/),
					ext = "part";
				if (matches) {
					ext = matches[1];
				}
				file.target_name = file.id + '.' + ext;
			}
		}


		function onError(up, err) {
			if (err.code === plupload.INIT_ERROR) {
				up.destroy();
			}
			else if (err.code === plupload.HTTP_ERROR && up.state == plupload.STARTED) {
				up.trigger('CancelUpload');
			}
		}


		function onDestroy(up) {
			up.forEachItem(function(file) {
				file.destroy();
			});

			if (_fileInputs.length) {
				plupload.each(_fileInputs, function(fileInput) {
					fileInput.destroy();
				});
				_fileInputs = [];
			}

			if (_fileDrops.length) {
				plupload.each(_fileDrops, function(fileDrop) {
					fileDrop.destroy();
				});
				_fileDrops = [];
			}

			_initialized = false;

			if (_queueUpload) {
				_queueUpload.destroy();
			}

			if (_queueResize) {
				_queueResize.destroy();
			}

			_options = _queueUpload = _queueResize = null; // purge these exclusively

		}

	}


	// convert plupload features to caps acceptable by mOxie
	function normalizeCaps(settings) {
		var features = settings.required_features,
			caps = {};

		function resolve(feature, value, strict) {
			// Feature notation is deprecated, use caps (this thing here is required for backward compatibility)
			var map = {
				chunks: 'slice_blob',
				jpgresize: 'send_binary_string',
				pngresize: 'send_binary_string',
				progress: 'report_upload_progress',
				multi_selection: 'select_multiple',
				dragdrop: 'drag_and_drop',
				drop_element: 'drag_and_drop',
				headers: 'send_custom_headers',
				urlstream_upload: 'send_binary_string',
				canSendBinary: 'send_binary',
				triggerDialog: 'summon_file_dialog'
			};

			if (map[feature]) {
				caps[map[feature]] = value;
			} else if (!strict) {
				caps[feature] = value;
			}
		}

		if (typeof(features) === 'string') {
			plupload.each(features.split(/\s*,\s*/), function(feature) {
				resolve(feature, true);
			});
		} else if (typeof(features) === 'object') {
			plupload.each(features, function(value, feature) {
				resolve(feature, value);
			});
		} else if (features === true) {
			// check settings for required features
			if (settings.chunk_size && settings.chunk_size > 0) {
				caps.slice_blob = true;
			}

			if (!plupload.isEmptyObj(settings.resize) || settings.multipart === false) {
				caps.send_binary_string = true;
			}

			if (settings.http_method) {
				caps.use_http_method = settings.http_method;
			}

			plupload.each(settings, function(value, feature) {
				resolve(feature, !!value, true); // strict check
			});
		}

		return caps;
	}

	function normalizeOptions(options) {
		plupload.each(options, function(value, option) {
			options[option] = normalizeOption(option, value, options);
		});
		return options;
	}

	/**
	Normalize an option.

	@method normalizeOption
	@private

	@param {String} option Name of the option to normalize
	@param {Mixed} value
	@param {Object} options The whole set of options, that might be modified during normalization (see max_file_size or unique_names)!
	*/
	function normalizeOption(option, value, options) {
		switch (option) {

			case 'chunk_size':
				if (value = plupload.parseSize(value)) {
					options.send_file_name = true;
				}
				break;

			case 'headers':
				var headers = {};
				if (typeof(value) === 'object') {
					plupload.each(value, function(value, key) {
						headers[key.toLowerCase()] = value;
					});
				}
				return headers;

			case 'http_method':
				return value.toUpperCase() === 'PUT' ? 'PUT' : 'POST';


			case 'filters':
				if (plupload.typeOf(value) === 'array') { // for backward compatibility
					value = {
						mime_types: value
					};
				}

				// if file format filters are being updated, regenerate the matching expressions
				if (value.mime_types) {
					if (plupload.typeOf(value.mime_types) === 'string') {
						value.mime_types = plupload.mimes2extList(value.mime_types);
					}

					// generate and cache regular expression for filtering file extensions
					options.re_ext_filter = (function(filters) {
						var extensionsRegExp = [];

						plupload.each(filters, function(filter) {
							plupload.each(filter.extensions.split(/,/), function(ext) {
								if (/^\s*\*\s*$/.test(ext)) {
									extensionsRegExp.push('\\.*');
								} else {
									extensionsRegExp.push('\\.' + ext.replace(new RegExp('[' + ('/^$.*+?|()[]{}\\'.replace(/./g, '\\$&')) + ']', 'g'), '\\$&'));
								}
							});
						});

						return new RegExp('(' + extensionsRegExp.join('|') + ')$', 'i');
					}(value.mime_types));
				}

				return value;

			case 'max_file_size':
				if (options && !options.filters) {
					options.filters = {};
				}
				options.filters.max_file_size = value;
				break;

			case 'multipart':
				if (!value) {
					options.send_file_name = true;
				}
				break;

			case 'multipart_params':
				options.params = options.multipart_params = value;
				break;

			case 'resize':
				if (value) {
					return plupload.extend({
						preserve_headers: true,
						crop: false
					}, value);
				}
				return false;

			case 'prevent_duplicates':
				if (options && !options.filters) {
					options.filters = {};
				}
				options.filters.prevent_duplicates = !!value;
				break;

			case 'unique_names':
				if (value) {
					options.send_file_name = true;
				}
				break;

			case 'required_features':
				// Normalize the list of required capabilities
				return normalizeCaps(plupload.extend({}, options));

			case 'preferred_caps':
				// Come up with the list of capabilities that can affect default mode in a multi-mode runtimes
				return normalizeCaps(plupload.extend({}, options, {
					required_features: true
				}));

				// options that require reinitialisation
			case 'container':
			case 'browse_button':
			case 'drop_element':
				return 'container' === option ? plupload.get(value) : plupload.getAll(value);
		}

		return value;
	}


	/**
	 * Registers a filter that will be executed for each file added to the queue.
	 * If callback returns false, file will not be added.
	 *
	 * Callback receives two arguments: a value for the filter as it was specified in settings.filters
	 * and a file to be filtered. Callback is executed in the context of uploader instance.
	 *
	 * @method addFileFilter
	 * @static
	 * @param {String} name Name of the filter by which it can be referenced in settings.filters
	 * @param {String} cb Callback - the actual routine that every added file must pass
	 */
	function addFileFilter(name, cb) {
		fileFilters[name] = cb;
	}


	/**
	 * A way to predict what runtime will be choosen in the current environment with the
	 * specified settings.
	 *
	 * @method predictRuntime
	 * @static
	 * @param {Object|String} config Plupload settings to check
	 * @param {String} [runtimes] Comma-separated list of runtimes to check against
	 * @return {String} Type of compatible runtime
	 */
	function predictRuntime(config, runtimes) {
		var up, runtime;

		up = new Uploader(config);
		runtime = plupload.Runtime.thatCan(up.getOption('required_features'), runtimes || config.runtimes);
		up.destroy();
		return runtime;
	}


	addFileFilter('mime_types', function(filters, file, cb) {
		if (filters.length && !this.getOption('re_ext_filter').test(file.name)) {
			this.trigger('Error', {
				code: plupload.FILE_EXTENSION_ERROR,
				message: plupload.translate('File extension error.'),
				file: file
			});
			cb(false);
		} else {
			cb(true);
		}
	});


	addFileFilter('max_file_size', function(maxSize, file, cb) {
		var undef;

		maxSize = plupload.parseSize(maxSize);

		// Invalid file size
		if (file.size !== undef && maxSize && file.size > maxSize) {
			this.trigger('Error', {
				code: plupload.FILE_SIZE_ERROR,
				message: plupload.translate('File size error.'),
				file: file
			});
			cb(false);
		} else {
			cb(true);
		}
	});


	addFileFilter('prevent_duplicates', function(value, file, cb) {
		var self = this;
		if (value) {
			this.forEachItem(function(item) {
				// Compare by name and size (size might be 0 or undefined, but still equivalent for both)
				if (file.name === item.name && file.size === item.size) {
					self.trigger('Error', {
						code: plupload.FILE_DUPLICATE_ERROR,
						message: plupload.translate('Duplicate file error.'),
						file: file
					});
					cb(false);
					return;
				}
			});
		}
		cb(true);
	});


	addFileFilter('prevent_empty', function(value, file, cb) {
		if (value && !file.size && file.size !== undef) {
			this.trigger('Error', {
				code : plupload.FILE_SIZE_ERROR,
				message : plupload.translate('File size error.'),
				file : file
			});
			cb(false);
		} else {
			cb(true);
		}
	});


	Uploader.addFileFilter = addFileFilter;

	plupload.inherit(Uploader, Queue);

	// for backward compatibility
	plupload.addFileFilter = addFileFilter;
	plupload.predictRuntime = predictRuntime;

	return Uploader;
});

expose(["plupload","plupload/core/Collection","plupload/core/ArrCollection","plupload/core/Optionable","plupload/core/Queueable","plupload/core/Stats","plupload/core/Queue","plupload/QueueUpload","plupload/QueueResize","plupload/ChunkUploader","plupload/FileUploader","plupload/ImageResizer","plupload/File","plupload/Uploader"]);
})(this);
}));