9902 lines
		
	
	
	
		
			248 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			9902 lines
		
	
	
	
		
			248 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | ;var MXI_DEBUG = false; | |||
|  | /** | |||
|  |  * mOxie - multi-runtime File API & XMLHttpRequest L2 Polyfill | |||
|  |  * v1.3.5 | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  * | |||
|  |  * Date: 2016-05-15 | |||
|  |  */ | |||
|  | /** | |||
|  |  * Compiled inline version. (Library mode) | |||
|  |  */ | |||
|  | 
 | |||
|  | /** | |||
|  |  * Modified for WordPress, Silverlight and Flash runtimes support was removed. | |||
|  |  * See https://core.trac.wordpress.org/ticket/41755.
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /*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/javascript/core/utils/Basic.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * Basic.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define('moxie/core/utils/Basic', [], function() { | |||
|  | 	/** | |||
|  | 	Gets the true type of the built-in object (better version of typeof). | |||
|  | 	@author Angus Croll (http://javascriptweblog.wordpress.com/)
 | |||
|  | 
 | |||
|  | 	@method typeOf | |||
|  | 	@for Utils | |||
|  | 	@static | |||
|  | 	@param {Object} o Object to check. | |||
|  | 	@return {String} Object [[Class]] | |||
|  | 	*/ | |||
|  | 	var typeOf = function(o) { | |||
|  | 		var undef; | |||
|  | 
 | |||
|  | 		if (o === undef) { | |||
|  | 			return 'undefined'; | |||
|  | 		} else if (o === null) { | |||
|  | 			return 'null'; | |||
|  | 		} else if (o.nodeType) { | |||
|  | 			return 'node'; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// the snippet below is awesome, however it fails to detect null, undefined and arguments types in IE lte 8
 | |||
|  | 		return ({}).toString.call(o).match(/\s([a-z|A-Z]+)/)[1].toLowerCase(); | |||
|  | 	}; | |||
|  | 		 | |||
|  | 	/** | |||
|  | 	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. | |||
|  | 	*/ | |||
|  | 	var extend = function(target) { | |||
|  | 		var undef; | |||
|  | 
 | |||
|  | 		each(arguments, function(arg, i) { | |||
|  | 			if (i > 0) { | |||
|  | 				each(arg, function(value, key) { | |||
|  | 					if (value !== undef) { | |||
|  | 						if (typeOf(target[key]) === typeOf(value) && !!~inArray(typeOf(value), ['array', 'object'])) { | |||
|  | 							extend(target[key], value); | |||
|  | 						} else { | |||
|  | 							target[key] = value; | |||
|  | 						} | |||
|  | 					} | |||
|  | 				}); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 		return target; | |||
|  | 	}; | |||
|  | 		 | |||
|  | 	/** | |||
|  | 	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. | |||
|  | 	*/ | |||
|  | 	var each = function(obj, callback) { | |||
|  | 		var length, key, i, undef; | |||
|  | 
 | |||
|  | 		if (obj) { | |||
|  | 			if (typeOf(obj.length) === 'number') { // it might be Array, FileList or even arguments object
 | |||
|  | 				// Loop array items
 | |||
|  | 				for (i = 0, length = obj.length; i < length; i++) { | |||
|  | 					if (callback(obj[i], i) === false) { | |||
|  | 						return; | |||
|  | 					} | |||
|  | 				} | |||
|  | 			} else if (typeOf(obj) === 'object') { | |||
|  | 				// Loop object items
 | |||
|  | 				for (key in obj) { | |||
|  | 					if (obj.hasOwnProperty(key)) { | |||
|  | 						if (callback(obj[key], key) === false) { | |||
|  | 							return; | |||
|  | 						} | |||
|  | 					} | |||
|  | 				} | |||
|  | 			} | |||
|  | 		} | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Checks if object is empty. | |||
|  | 	 | |||
|  | 	@method isEmptyObj | |||
|  | 	@static | |||
|  | 	@param {Object} o Object to check. | |||
|  | 	@return {Boolean} | |||
|  | 	*/ | |||
|  | 	var isEmptyObj = function(obj) { | |||
|  | 		var prop; | |||
|  | 
 | |||
|  | 		if (!obj || typeOf(obj) !== 'object') { | |||
|  | 			return true; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		for (prop in obj) { | |||
|  | 			return false; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		return true; | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	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 | |||
|  | 	*/ | |||
|  | 	var inSeries = function(queue, cb) { | |||
|  | 		var i = 0, length = queue.length; | |||
|  | 
 | |||
|  | 		if (typeOf(cb) !== 'function') { | |||
|  | 			cb = function() {}; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		if (!queue || !queue.length) { | |||
|  | 			cb(); | |||
|  | 		} | |||
|  | 
 | |||
|  | 		function callNext(i) { | |||
|  | 			if (typeOf(queue[i]) === 'function') { | |||
|  | 				queue[i](function(error) { | |||
|  | 					/*jshint expr:true */ | |||
|  | 					++i < length && !error ? callNext(i) : cb(error); | |||
|  | 				}); | |||
|  | 			} | |||
|  | 		} | |||
|  | 		callNext(i); | |||
|  | 	}; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	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 error | |||
|  | 	*/ | |||
|  | 	var inParallel = function(queue, cb) { | |||
|  | 		var count = 0, num = queue.length, cbArgs = new Array(num); | |||
|  | 
 | |||
|  | 		each(queue, function(fn, i) { | |||
|  | 			fn(function(error) { | |||
|  | 				if (error) { | |||
|  | 					return cb(error); | |||
|  | 				} | |||
|  | 				 | |||
|  | 				var args = [].slice.call(arguments); | |||
|  | 				args.shift(); // strip error - undefined or not
 | |||
|  | 
 | |||
|  | 				cbArgs[i] = args; | |||
|  | 				count++; | |||
|  | 
 | |||
|  | 				if (count === num) { | |||
|  | 					cbArgs.unshift(null); | |||
|  | 					cb.apply(this, cbArgs); | |||
|  | 				}  | |||
|  | 			}); | |||
|  | 		}); | |||
|  | 	}; | |||
|  | 	 | |||
|  | 	 | |||
|  | 	/** | |||
|  | 	Find an element in array and return it's 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 | |||
|  | 	*/ | |||
|  | 	var inArray = function(needle, array) { | |||
|  | 		if (array) { | |||
|  | 			if (Array.prototype.indexOf) { | |||
|  | 				return Array.prototype.indexOf.call(array, needle); | |||
|  | 			} | |||
|  | 		 | |||
|  | 			for (var i = 0, length = array.length; i < length; i++) { | |||
|  | 				if (array[i] === needle) { | |||
|  | 					return i; | |||
|  | 				} | |||
|  | 			} | |||
|  | 		} | |||
|  | 		return -1; | |||
|  | 	}; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Returns elements of first array if they are not present in second. And false - otherwise. | |||
|  | 
 | |||
|  | 	@private | |||
|  | 	@method arrayDiff | |||
|  | 	@param {Array} needles | |||
|  | 	@param {Array} array | |||
|  | 	@return {Array|Boolean} | |||
|  | 	*/ | |||
|  | 	var arrayDiff = function(needles, array) { | |||
|  | 		var diff = []; | |||
|  | 
 | |||
|  | 		if (typeOf(needles) !== 'array') { | |||
|  | 			needles = [needles]; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		if (typeOf(array) !== 'array') { | |||
|  | 			array = [array]; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		for (var i in needles) { | |||
|  | 			if (inArray(needles[i], array) === -1) { | |||
|  | 				diff.push(needles[i]); | |||
|  | 			}	 | |||
|  | 		} | |||
|  | 		return diff.length ? diff : false; | |||
|  | 	}; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Find intersection of two arrays. | |||
|  | 
 | |||
|  | 	@private | |||
|  | 	@method arrayIntersect | |||
|  | 	@param {Array} array1 | |||
|  | 	@param {Array} array2 | |||
|  | 	@return {Array} Intersection of two arrays or null if there is none | |||
|  | 	*/ | |||
|  | 	var arrayIntersect = function(array1, array2) { | |||
|  | 		var result = []; | |||
|  | 		each(array1, function(item) { | |||
|  | 			if (inArray(item, array2) !== -1) { | |||
|  | 				result.push(item); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 		return result.length ? result : null; | |||
|  | 	}; | |||
|  | 	 | |||
|  | 	 | |||
|  | 	/** | |||
|  | 	Forces anything into an array. | |||
|  | 	 | |||
|  | 	@method toArray | |||
|  | 	@static | |||
|  | 	@param {Object} obj Object with length field. | |||
|  | 	@return {Array} Array object containing all items. | |||
|  | 	*/ | |||
|  | 	var toArray = function(obj) { | |||
|  | 		var i, arr = []; | |||
|  | 
 | |||
|  | 		for (i = 0; i < obj.length; i++) { | |||
|  | 			arr[i] = obj[i]; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		return arr; | |||
|  | 	}; | |||
|  | 	 | |||
|  | 			 | |||
|  | 	/** | |||
|  | 	Generates an unique ID. The only way a user would be able to get the same ID is if the two persons | |||
|  | 	at the same exact millisecond manage to get the same 5 random numbers between 0-65535; it also uses  | |||
|  | 	a counter so each ID is guaranteed to be unique for the given page. It is more probable for the earth  | |||
|  | 	to be hit with an asteroid. | |||
|  | 	 | |||
|  | 	@method guid | |||
|  | 	@static | |||
|  | 	@param {String} prefix to prepend (by default 'o' will be prepended). | |||
|  | 	@method guid | |||
|  | 	@return {String} Virtually unique id. | |||
|  | 	*/ | |||
|  | 	var guid = (function() { | |||
|  | 		var counter = 0; | |||
|  | 		 | |||
|  | 		return function(prefix) { | |||
|  | 			var guid = new Date().getTime().toString(32), i; | |||
|  | 
 | |||
|  | 			for (i = 0; i < 5; i++) { | |||
|  | 				guid += Math.floor(Math.random() * 65535).toString(32); | |||
|  | 			} | |||
|  | 			 | |||
|  | 			return (prefix || 'o_') + guid + (counter++).toString(32); | |||
|  | 		}; | |||
|  | 	}()); | |||
|  | 	 | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Trims white spaces around the string | |||
|  | 	 | |||
|  | 	@method trim | |||
|  | 	@static | |||
|  | 	@param {String} str | |||
|  | 	@return {String} | |||
|  | 	*/ | |||
|  | 	var trim = function(str) { | |||
|  | 		if (!str) { | |||
|  | 			return str; | |||
|  | 		} | |||
|  | 		return String.prototype.trim ? String.prototype.trim.call(str) : str.toString().replace(/^\s*/, '').replace(/\s*$/, ''); | |||
|  | 	}; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Parses the specified size string into a byte value. For example 10kb becomes 10240. | |||
|  | 	 | |||
|  | 	@method parseSizeStr | |||
|  | 	@static | |||
|  | 	@param {String/Number} size String to parse or number to just pass through. | |||
|  | 	@return {Number} Size in bytes. | |||
|  | 	*/ | |||
|  | 	var parseSizeStr = function(size) { | |||
|  | 		if (typeof(size) !== 'string') { | |||
|  | 			return size; | |||
|  | 		} | |||
|  | 		 | |||
|  | 		var muls = { | |||
|  | 				t: 1099511627776, | |||
|  | 				g: 1073741824, | |||
|  | 				m: 1048576, | |||
|  | 				k: 1024 | |||
|  | 			}, | |||
|  | 			mul; | |||
|  | 
 | |||
|  | 
 | |||
|  | 		size = /^([0-9\.]+)([tmgk]?)$/.exec(size.toLowerCase().replace(/[^0-9\.tmkg]/g, '')); | |||
|  | 		mul = size[2]; | |||
|  | 		size = +size[1]; | |||
|  | 		 | |||
|  | 		if (muls.hasOwnProperty(mul)) { | |||
|  | 			size *= muls[mul]; | |||
|  | 		} | |||
|  | 		return Math.floor(size); | |||
|  | 	}; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	 * Pseudo sprintf implementation - simple way to replace tokens with specified values. | |||
|  | 	 * | |||
|  | 	 * @param {String} str String with tokens | |||
|  | 	 * @return {String} String with replaced tokens | |||
|  | 	 */ | |||
|  | 	var sprintf = function(str) { | |||
|  | 		var args = [].slice.call(arguments, 1); | |||
|  | 
 | |||
|  | 		return str.replace(/%[a-z]/g, function() { | |||
|  | 			var value = args.shift(); | |||
|  | 			return typeOf(value) !== 'undefined' ? value : ''; | |||
|  | 		}); | |||
|  | 	}; | |||
|  | 	 | |||
|  | 
 | |||
|  | 	return { | |||
|  | 		guid: guid, | |||
|  | 		typeOf: typeOf, | |||
|  | 		extend: extend, | |||
|  | 		each: each, | |||
|  | 		isEmptyObj: isEmptyObj, | |||
|  | 		inSeries: inSeries, | |||
|  | 		inParallel: inParallel, | |||
|  | 		inArray: inArray, | |||
|  | 		arrayDiff: arrayDiff, | |||
|  | 		arrayIntersect: arrayIntersect, | |||
|  | 		toArray: toArray, | |||
|  | 		trim: trim, | |||
|  | 		sprintf: sprintf, | |||
|  | 		parseSizeStr: parseSizeStr | |||
|  | 	}; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/core/utils/Env.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * Env.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define("moxie/core/utils/Env", [ | |||
|  | 	"moxie/core/utils/Basic" | |||
|  | ], function(Basic) { | |||
|  | 	 | |||
|  | 	/** | |||
|  | 	 * UAParser.js v0.7.7 | |||
|  | 	 * Lightweight JavaScript-based User-Agent string parser | |||
|  | 	 * https://github.com/faisalman/ua-parser-js
 | |||
|  | 	 * | |||
|  | 	 * Copyright © 2012-2015 Faisal Salman <fyzlman@gmail.com> | |||
|  | 	 * Dual licensed under GPLv2 & MIT | |||
|  | 	 */ | |||
|  | 	var UAParser = (function (undefined) { | |||
|  | 
 | |||
|  | 	    //////////////
 | |||
|  | 	    // Constants
 | |||
|  | 	    /////////////
 | |||
|  | 
 | |||
|  | 
 | |||
|  | 	    var EMPTY       = '', | |||
|  | 	        UNKNOWN     = '?', | |||
|  | 	        FUNC_TYPE   = 'function', | |||
|  | 	        UNDEF_TYPE  = 'undefined', | |||
|  | 	        OBJ_TYPE    = 'object', | |||
|  | 	        MAJOR       = 'major', | |||
|  | 	        MODEL       = 'model', | |||
|  | 	        NAME        = 'name', | |||
|  | 	        TYPE        = 'type', | |||
|  | 	        VENDOR      = 'vendor', | |||
|  | 	        VERSION     = 'version', | |||
|  | 	        ARCHITECTURE= 'architecture', | |||
|  | 	        CONSOLE     = 'console', | |||
|  | 	        MOBILE      = 'mobile', | |||
|  | 	        TABLET      = 'tablet'; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	    ///////////
 | |||
|  | 	    // Helper
 | |||
|  | 	    //////////
 | |||
|  | 
 | |||
|  | 
 | |||
|  | 	    var util = { | |||
|  | 	        has : function (str1, str2) { | |||
|  | 	            return str2.toLowerCase().indexOf(str1.toLowerCase()) !== -1; | |||
|  | 	        }, | |||
|  | 	        lowerize : function (str) { | |||
|  | 	            return str.toLowerCase(); | |||
|  | 	        } | |||
|  | 	    }; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	    ///////////////
 | |||
|  | 	    // Map helper
 | |||
|  | 	    //////////////
 | |||
|  | 
 | |||
|  | 
 | |||
|  | 	    var mapper = { | |||
|  | 
 | |||
|  | 	        rgx : function () { | |||
|  | 
 | |||
|  | 	            // loop through all regexes maps
 | |||
|  | 	            for (var result, i = 0, j, k, p, q, matches, match, args = arguments; i < args.length; i += 2) { | |||
|  | 
 | |||
|  | 	                var regex = args[i],       // even sequence (0,2,4,..)
 | |||
|  | 	                    props = args[i + 1];   // odd sequence (1,3,5,..)
 | |||
|  | 
 | |||
|  | 	                // construct object barebones
 | |||
|  | 	                if (typeof(result) === UNDEF_TYPE) { | |||
|  | 	                    result = {}; | |||
|  | 	                    for (p in props) { | |||
|  | 	                        q = props[p]; | |||
|  | 	                        if (typeof(q) === OBJ_TYPE) { | |||
|  | 	                            result[q[0]] = undefined; | |||
|  | 	                        } else { | |||
|  | 	                            result[q] = undefined; | |||
|  | 	                        } | |||
|  | 	                    } | |||
|  | 	                } | |||
|  | 
 | |||
|  | 	                // try matching uastring with regexes
 | |||
|  | 	                for (j = k = 0; j < regex.length; j++) { | |||
|  | 	                    matches = regex[j].exec(this.getUA()); | |||
|  | 	                    if (!!matches) { | |||
|  | 	                        for (p = 0; p < props.length; p++) { | |||
|  | 	                            match = matches[++k]; | |||
|  | 	                            q = props[p]; | |||
|  | 	                            // check if given property is actually array
 | |||
|  | 	                            if (typeof(q) === OBJ_TYPE && q.length > 0) { | |||
|  | 	                                if (q.length == 2) { | |||
|  | 	                                    if (typeof(q[1]) == FUNC_TYPE) { | |||
|  | 	                                        // assign modified match
 | |||
|  | 	                                        result[q[0]] = q[1].call(this, match); | |||
|  | 	                                    } else { | |||
|  | 	                                        // assign given value, ignore regex match
 | |||
|  | 	                                        result[q[0]] = q[1]; | |||
|  | 	                                    } | |||
|  | 	                                } else if (q.length == 3) { | |||
|  | 	                                    // check whether function or regex
 | |||
|  | 	                                    if (typeof(q[1]) === FUNC_TYPE && !(q[1].exec && q[1].test)) { | |||
|  | 	                                        // call function (usually string mapper)
 | |||
|  | 	                                        result[q[0]] = match ? q[1].call(this, match, q[2]) : undefined; | |||
|  | 	                                    } else { | |||
|  | 	                                        // sanitize match using given regex
 | |||
|  | 	                                        result[q[0]] = match ? match.replace(q[1], q[2]) : undefined; | |||
|  | 	                                    } | |||
|  | 	                                } else if (q.length == 4) { | |||
|  | 	                                        result[q[0]] = match ? q[3].call(this, match.replace(q[1], q[2])) : undefined; | |||
|  | 	                                } | |||
|  | 	                            } else { | |||
|  | 	                                result[q] = match ? match : undefined; | |||
|  | 	                            } | |||
|  | 	                        } | |||
|  | 	                        break; | |||
|  | 	                    } | |||
|  | 	                } | |||
|  | 
 | |||
|  | 	                if(!!matches) break; // break the loop immediately if match found
 | |||
|  | 	            } | |||
|  | 	            return result; | |||
|  | 	        }, | |||
|  | 
 | |||
|  | 	        str : function (str, map) { | |||
|  | 
 | |||
|  | 	            for (var i in map) { | |||
|  | 	                // check if array
 | |||
|  | 	                if (typeof(map[i]) === OBJ_TYPE && map[i].length > 0) { | |||
|  | 	                    for (var j = 0; j < map[i].length; j++) { | |||
|  | 	                        if (util.has(map[i][j], str)) { | |||
|  | 	                            return (i === UNKNOWN) ? undefined : i; | |||
|  | 	                        } | |||
|  | 	                    } | |||
|  | 	                } else if (util.has(map[i], str)) { | |||
|  | 	                    return (i === UNKNOWN) ? undefined : i; | |||
|  | 	                } | |||
|  | 	            } | |||
|  | 	            return str; | |||
|  | 	        } | |||
|  | 	    }; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	    ///////////////
 | |||
|  | 	    // String map
 | |||
|  | 	    //////////////
 | |||
|  | 
 | |||
|  | 
 | |||
|  | 	    var maps = { | |||
|  | 
 | |||
|  | 	        browser : { | |||
|  | 	            oldsafari : { | |||
|  | 	                major : { | |||
|  | 	                    '1' : ['/8', '/1', '/3'], | |||
|  | 	                    '2' : '/4', | |||
|  | 	                    '?' : '/' | |||
|  | 	                }, | |||
|  | 	                version : { | |||
|  | 	                    '1.0'   : '/8', | |||
|  | 	                    '1.2'   : '/1', | |||
|  | 	                    '1.3'   : '/3', | |||
|  | 	                    '2.0'   : '/412', | |||
|  | 	                    '2.0.2' : '/416', | |||
|  | 	                    '2.0.3' : '/417', | |||
|  | 	                    '2.0.4' : '/419', | |||
|  | 	                    '?'     : '/' | |||
|  | 	                } | |||
|  | 	            } | |||
|  | 	        }, | |||
|  | 
 | |||
|  | 	        device : { | |||
|  | 	            sprint : { | |||
|  | 	                model : { | |||
|  | 	                    'Evo Shift 4G' : '7373KT' | |||
|  | 	                }, | |||
|  | 	                vendor : { | |||
|  | 	                    'HTC'       : 'APA', | |||
|  | 	                    'Sprint'    : 'Sprint' | |||
|  | 	                } | |||
|  | 	            } | |||
|  | 	        }, | |||
|  | 
 | |||
|  | 	        os : { | |||
|  | 	            windows : { | |||
|  | 	                version : { | |||
|  | 	                    'ME'        : '4.90', | |||
|  | 	                    'NT 3.11'   : 'NT3.51', | |||
|  | 	                    'NT 4.0'    : 'NT4.0', | |||
|  | 	                    '2000'      : 'NT 5.0', | |||
|  | 	                    'XP'        : ['NT 5.1', 'NT 5.2'], | |||
|  | 	                    'Vista'     : 'NT 6.0', | |||
|  | 	                    '7'         : 'NT 6.1', | |||
|  | 	                    '8'         : 'NT 6.2', | |||
|  | 	                    '8.1'       : 'NT 6.3', | |||
|  | 	                    'RT'        : 'ARM' | |||
|  | 	                } | |||
|  | 	            } | |||
|  | 	        } | |||
|  | 	    }; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	    //////////////
 | |||
|  | 	    // Regex map
 | |||
|  | 	    /////////////
 | |||
|  | 
 | |||
|  | 
 | |||
|  | 	    var regexes = { | |||
|  | 
 | |||
|  | 	        browser : [[ | |||
|  | 	         | |||
|  | 	            // Presto based
 | |||
|  | 	            /(opera\smini)\/([\w\.-]+)/i,                                       // Opera Mini
 | |||
|  | 	            /(opera\s[mobiletab]+).+version\/([\w\.-]+)/i,                      // Opera Mobi/Tablet
 | |||
|  | 	            /(opera).+version\/([\w\.]+)/i,                                     // Opera > 9.80
 | |||
|  | 	            /(opera)[\/\s]+([\w\.]+)/i                                          // Opera < 9.80
 | |||
|  | 
 | |||
|  | 	            ], [NAME, VERSION], [ | |||
|  | 
 | |||
|  | 	            /\s(opr)\/([\w\.]+)/i                                               // Opera Webkit
 | |||
|  | 	            ], [[NAME, 'Opera'], VERSION], [ | |||
|  | 
 | |||
|  | 	            // Mixed
 | |||
|  | 	            /(kindle)\/([\w\.]+)/i,                                             // Kindle
 | |||
|  | 	            /(lunascape|maxthon|netfront|jasmine|blazer)[\/\s]?([\w\.]+)*/i, | |||
|  | 	                                                                                // Lunascape/Maxthon/Netfront/Jasmine/Blazer
 | |||
|  | 
 | |||
|  | 	            // Trident based
 | |||
|  | 	            /(avant\s|iemobile|slim|baidu)(?:browser)?[\/\s]?([\w\.]*)/i, | |||
|  | 	                                                                                // Avant/IEMobile/SlimBrowser/Baidu
 | |||
|  | 	            /(?:ms|\()(ie)\s([\w\.]+)/i,                                        // Internet Explorer
 | |||
|  | 
 | |||
|  | 	            // Webkit/KHTML based
 | |||
|  | 	            /(rekonq)\/([\w\.]+)*/i,                                            // Rekonq
 | |||
|  | 	            /(chromium|flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi)\/([\w\.-]+)/i | |||
|  | 	                                                                                // Chromium/Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron
 | |||
|  | 	            ], [NAME, VERSION], [ | |||
|  | 
 | |||
|  | 	            /(trident).+rv[:\s]([\w\.]+).+like\sgecko/i                         // IE11
 | |||
|  | 	            ], [[NAME, 'IE'], VERSION], [ | |||
|  | 
 | |||
|  | 	            /(edge)\/((\d+)?[\w\.]+)/i                                          // Microsoft Edge
 | |||
|  | 	            ], [NAME, VERSION], [ | |||
|  | 
 | |||
|  | 	            /(yabrowser)\/([\w\.]+)/i                                           // Yandex
 | |||
|  | 	            ], [[NAME, 'Yandex'], VERSION], [ | |||
|  | 
 | |||
|  | 	            /(comodo_dragon)\/([\w\.]+)/i                                       // Comodo Dragon
 | |||
|  | 	            ], [[NAME, /_/g, ' '], VERSION], [ | |||
|  | 
 | |||
|  | 	            /(chrome|omniweb|arora|[tizenoka]{5}\s?browser)\/v?([\w\.]+)/i, | |||
|  | 	                                                                                // Chrome/OmniWeb/Arora/Tizen/Nokia
 | |||
|  | 	            /(uc\s?browser|qqbrowser)[\/\s]?([\w\.]+)/i | |||
|  | 	                                                                                // UCBrowser/QQBrowser
 | |||
|  | 	            ], [NAME, VERSION], [ | |||
|  | 
 | |||
|  | 	            /(dolfin)\/([\w\.]+)/i                                              // Dolphin
 | |||
|  | 	            ], [[NAME, 'Dolphin'], VERSION], [ | |||
|  | 
 | |||
|  | 	            /((?:android.+)crmo|crios)\/([\w\.]+)/i                             // Chrome for Android/iOS
 | |||
|  | 	            ], [[NAME, 'Chrome'], VERSION], [ | |||
|  | 
 | |||
|  | 	            /XiaoMi\/MiuiBrowser\/([\w\.]+)/i                                   // MIUI Browser
 | |||
|  | 	            ], [VERSION, [NAME, 'MIUI Browser']], [ | |||
|  | 
 | |||
|  | 	            /android.+version\/([\w\.]+)\s+(?:mobile\s?safari|safari)/i         // Android Browser
 | |||
|  | 	            ], [VERSION, [NAME, 'Android Browser']], [ | |||
|  | 
 | |||
|  | 	            /FBAV\/([\w\.]+);/i                                                 // Facebook App for iOS
 | |||
|  | 	            ], [VERSION, [NAME, 'Facebook']], [ | |||
|  | 
 | |||
|  | 	            /version\/([\w\.]+).+?mobile\/\w+\s(safari)/i                       // Mobile Safari
 | |||
|  | 	            ], [VERSION, [NAME, 'Mobile Safari']], [ | |||
|  | 
 | |||
|  | 	            /version\/([\w\.]+).+?(mobile\s?safari|safari)/i                    // Safari & Safari Mobile
 | |||
|  | 	            ], [VERSION, NAME], [ | |||
|  | 
 | |||
|  | 	            /webkit.+?(mobile\s?safari|safari)(\/[\w\.]+)/i                     // Safari < 3.0
 | |||
|  | 	            ], [NAME, [VERSION, mapper.str, maps.browser.oldsafari.version]], [ | |||
|  | 
 | |||
|  | 	            /(konqueror)\/([\w\.]+)/i,                                          // Konqueror
 | |||
|  | 	            /(webkit|khtml)\/([\w\.]+)/i | |||
|  | 	            ], [NAME, VERSION], [ | |||
|  | 
 | |||
|  | 	            // Gecko based
 | |||
|  | 	            /(navigator|netscape)\/([\w\.-]+)/i                                 // Netscape
 | |||
|  | 	            ], [[NAME, 'Netscape'], VERSION], [ | |||
|  | 	            /(swiftfox)/i,                                                      // Swiftfox
 | |||
|  | 	            /(icedragon|iceweasel|camino|chimera|fennec|maemo\sbrowser|minimo|conkeror)[\/\s]?([\w\.\+]+)/i, | |||
|  | 	                                                                                // IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror
 | |||
|  | 	            /(firefox|seamonkey|k-meleon|icecat|iceape|firebird|phoenix)\/([\w\.-]+)/i, | |||
|  | 	                                                                                // Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix
 | |||
|  | 	            /(mozilla)\/([\w\.]+).+rv\:.+gecko\/\d+/i,                          // Mozilla
 | |||
|  | 
 | |||
|  | 	            // Other
 | |||
|  | 	            /(polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf)[\/\s]?([\w\.]+)/i, | |||
|  | 	                                                                                // Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf
 | |||
|  | 	            /(links)\s\(([\w\.]+)/i,                                            // Links
 | |||
|  | 	            /(gobrowser)\/?([\w\.]+)*/i,                                        // GoBrowser
 | |||
|  | 	            /(ice\s?browser)\/v?([\w\._]+)/i,                                   // ICE Browser
 | |||
|  | 	            /(mosaic)[\/\s]([\w\.]+)/i                                          // Mosaic
 | |||
|  | 	            ], [NAME, VERSION] | |||
|  | 	        ], | |||
|  | 
 | |||
|  | 	        engine : [[ | |||
|  | 
 | |||
|  | 	            /windows.+\sedge\/([\w\.]+)/i                                       // EdgeHTML
 | |||
|  | 	            ], [VERSION, [NAME, 'EdgeHTML']], [ | |||
|  | 
 | |||
|  | 	            /(presto)\/([\w\.]+)/i,                                             // Presto
 | |||
|  | 	            /(webkit|trident|netfront|netsurf|amaya|lynx|w3m)\/([\w\.]+)/i,     // WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m
 | |||
|  | 	            /(khtml|tasman|links)[\/\s]\(?([\w\.]+)/i,                          // KHTML/Tasman/Links
 | |||
|  | 	            /(icab)[\/\s]([23]\.[\d\.]+)/i                                      // iCab
 | |||
|  | 	            ], [NAME, VERSION], [ | |||
|  | 
 | |||
|  | 	            /rv\:([\w\.]+).*(gecko)/i                                           // Gecko
 | |||
|  | 	            ], [VERSION, NAME] | |||
|  | 	        ], | |||
|  | 
 | |||
|  | 	        os : [[ | |||
|  | 
 | |||
|  | 	            // Windows based
 | |||
|  | 	            /microsoft\s(windows)\s(vista|xp)/i                                 // Windows (iTunes)
 | |||
|  | 	            ], [NAME, VERSION], [ | |||
|  | 	            /(windows)\snt\s6\.2;\s(arm)/i,                                     // Windows RT
 | |||
|  | 	            /(windows\sphone(?:\sos)*|windows\smobile|windows)[\s\/]?([ntce\d\.\s]+\w)/i | |||
|  | 	            ], [NAME, [VERSION, mapper.str, maps.os.windows.version]], [ | |||
|  | 	            /(win(?=3|9|n)|win\s9x\s)([nt\d\.]+)/i | |||
|  | 	            ], [[NAME, 'Windows'], [VERSION, mapper.str, maps.os.windows.version]], [ | |||
|  | 
 | |||
|  | 	            // Mobile/Embedded OS
 | |||
|  | 	            /\((bb)(10);/i                                                      // BlackBerry 10
 | |||
|  | 	            ], [[NAME, 'BlackBerry'], VERSION], [ | |||
|  | 	            /(blackberry)\w*\/?([\w\.]+)*/i,                                    // Blackberry
 | |||
|  | 	            /(tizen)[\/\s]([\w\.]+)/i,                                          // Tizen
 | |||
|  | 	            /(android|webos|palm\os|qnx|bada|rim\stablet\sos|meego|contiki)[\/\s-]?([\w\.]+)*/i, | |||
|  | 	                                                                                // Android/WebOS/Palm/QNX/Bada/RIM/MeeGo/Contiki
 | |||
|  | 	            /linux;.+(sailfish);/i                                              // Sailfish OS
 | |||
|  | 	            ], [NAME, VERSION], [ | |||
|  | 	            /(symbian\s?os|symbos|s60(?=;))[\/\s-]?([\w\.]+)*/i                 // Symbian
 | |||
|  | 	            ], [[NAME, 'Symbian'], VERSION], [ | |||
|  | 	            /\((series40);/i                                                    // Series 40
 | |||
|  | 	            ], [NAME], [ | |||
|  | 	            /mozilla.+\(mobile;.+gecko.+firefox/i                               // Firefox OS
 | |||
|  | 	            ], [[NAME, 'Firefox OS'], VERSION], [ | |||
|  | 
 | |||
|  | 	            // Console
 | |||
|  | 	            /(nintendo|playstation)\s([wids3portablevu]+)/i,                    // Nintendo/Playstation
 | |||
|  | 
 | |||
|  | 	            // GNU/Linux based
 | |||
|  | 	            /(mint)[\/\s\(]?(\w+)*/i,                                           // Mint
 | |||
|  | 	            /(mageia|vectorlinux)[;\s]/i,                                       // Mageia/VectorLinux
 | |||
|  | 	            /(joli|[kxln]?ubuntu|debian|[open]*suse|gentoo|arch|slackware|fedora|mandriva|centos|pclinuxos|redhat|zenwalk|linpus)[\/\s-]?([\w\.-]+)*/i, | |||
|  | 	                                                                                // Joli/Ubuntu/Debian/SUSE/Gentoo/Arch/Slackware
 | |||
|  | 	                                                                                // Fedora/Mandriva/CentOS/PCLinuxOS/RedHat/Zenwalk/Linpus
 | |||
|  | 	            /(hurd|linux)\s?([\w\.]+)*/i,                                       // Hurd/Linux
 | |||
|  | 	            /(gnu)\s?([\w\.]+)*/i                                               // GNU
 | |||
|  | 	            ], [NAME, VERSION], [ | |||
|  | 
 | |||
|  | 	            /(cros)\s[\w]+\s([\w\.]+\w)/i                                       // Chromium OS
 | |||
|  | 	            ], [[NAME, 'Chromium OS'], VERSION],[ | |||
|  | 
 | |||
|  | 	            // Solaris
 | |||
|  | 	            /(sunos)\s?([\w\.]+\d)*/i                                           // Solaris
 | |||
|  | 	            ], [[NAME, 'Solaris'], VERSION], [ | |||
|  | 
 | |||
|  | 	            // BSD based
 | |||
|  | 	            /\s([frentopc-]{0,4}bsd|dragonfly)\s?([\w\.]+)*/i                   // FreeBSD/NetBSD/OpenBSD/PC-BSD/DragonFly
 | |||
|  | 	            ], [NAME, VERSION],[ | |||
|  | 
 | |||
|  | 	            /(ip[honead]+)(?:.*os\s*([\w]+)*\slike\smac|;\sopera)/i             // iOS
 | |||
|  | 	            ], [[NAME, 'iOS'], [VERSION, /_/g, '.']], [ | |||
|  | 
 | |||
|  | 	            /(mac\sos\sx)\s?([\w\s\.]+\w)*/i, | |||
|  | 	            /(macintosh|mac(?=_powerpc)\s)/i                                    // Mac OS
 | |||
|  | 	            ], [[NAME, 'Mac OS'], [VERSION, /_/g, '.']], [ | |||
|  | 
 | |||
|  | 	            // Other
 | |||
|  | 	            /((?:open)?solaris)[\/\s-]?([\w\.]+)*/i,                            // Solaris
 | |||
|  | 	            /(haiku)\s(\w+)/i,                                                  // Haiku
 | |||
|  | 	            /(aix)\s((\d)(?=\.|\)|\s)[\w\.]*)*/i,                               // AIX
 | |||
|  | 	            /(plan\s9|minix|beos|os\/2|amigaos|morphos|risc\sos|openvms)/i, | |||
|  | 	                                                                                // Plan9/Minix/BeOS/OS2/AmigaOS/MorphOS/RISCOS/OpenVMS
 | |||
|  | 	            /(unix)\s?([\w\.]+)*/i                                              // UNIX
 | |||
|  | 	            ], [NAME, VERSION] | |||
|  | 	        ] | |||
|  | 	    }; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	    /////////////////
 | |||
|  | 	    // Constructor
 | |||
|  | 	    ////////////////
 | |||
|  | 
 | |||
|  | 
 | |||
|  | 	    var UAParser = function (uastring) { | |||
|  | 
 | |||
|  | 	        var ua = uastring || ((window && window.navigator && window.navigator.userAgent) ? window.navigator.userAgent : EMPTY); | |||
|  | 
 | |||
|  | 	        this.getBrowser = function () { | |||
|  | 	            return mapper.rgx.apply(this, regexes.browser); | |||
|  | 	        }; | |||
|  | 	        this.getEngine = function () { | |||
|  | 	            return mapper.rgx.apply(this, regexes.engine); | |||
|  | 	        }; | |||
|  | 	        this.getOS = function () { | |||
|  | 	            return mapper.rgx.apply(this, regexes.os); | |||
|  | 	        }; | |||
|  | 	        this.getResult = function() { | |||
|  | 	            return { | |||
|  | 	                ua      : this.getUA(), | |||
|  | 	                browser : this.getBrowser(), | |||
|  | 	                engine  : this.getEngine(), | |||
|  | 	                os      : this.getOS() | |||
|  | 	            }; | |||
|  | 	        }; | |||
|  | 	        this.getUA = function () { | |||
|  | 	            return ua; | |||
|  | 	        }; | |||
|  | 	        this.setUA = function (uastring) { | |||
|  | 	            ua = uastring; | |||
|  | 	            return this; | |||
|  | 	        }; | |||
|  | 	        this.setUA(ua); | |||
|  | 	    }; | |||
|  | 
 | |||
|  | 	    return UAParser; | |||
|  | 	})(); | |||
|  | 
 | |||
|  | 
 | |||
|  | 	function version_compare(v1, v2, operator) { | |||
|  | 	  // From: http://phpjs.org/functions
 | |||
|  | 	  // +      original by: Philippe Jausions (http://pear.php.net/user/jausions)
 | |||
|  | 	  // +      original by: Aidan Lister (http://aidanlister.com/)
 | |||
|  | 	  // + reimplemented by: Kankrelune (http://www.webfaktory.info/)
 | |||
|  | 	  // +      improved by: Brett Zamir (http://brett-zamir.me)
 | |||
|  | 	  // +      improved by: Scott Baker
 | |||
|  | 	  // +      improved by: Theriault
 | |||
|  | 	  // *        example 1: version_compare('8.2.5rc', '8.2.5a');
 | |||
|  | 	  // *        returns 1: 1
 | |||
|  | 	  // *        example 2: version_compare('8.2.50', '8.2.52', '<');
 | |||
|  | 	  // *        returns 2: true
 | |||
|  | 	  // *        example 3: version_compare('5.3.0-dev', '5.3.0');
 | |||
|  | 	  // *        returns 3: -1
 | |||
|  | 	  // *        example 4: version_compare('4.1.0.52','4.01.0.51');
 | |||
|  | 	  // *        returns 4: 1
 | |||
|  | 
 | |||
|  | 	  // Important: compare must be initialized at 0.
 | |||
|  | 	  var i = 0, | |||
|  | 	    x = 0, | |||
|  | 	    compare = 0, | |||
|  | 	    // vm maps textual PHP versions to negatives so they're less than 0.
 | |||
|  | 	    // PHP currently defines these as CASE-SENSITIVE. It is important to
 | |||
|  | 	    // leave these as negatives so that they can come before numerical versions
 | |||
|  | 	    // and as if no letters were there to begin with.
 | |||
|  | 	    // (1alpha is < 1 and < 1.1 but > 1dev1)
 | |||
|  | 	    // If a non-numerical value can't be mapped to this table, it receives
 | |||
|  | 	    // -7 as its value.
 | |||
|  | 	    vm = { | |||
|  | 	      'dev': -6, | |||
|  | 	      'alpha': -5, | |||
|  | 	      'a': -5, | |||
|  | 	      'beta': -4, | |||
|  | 	      'b': -4, | |||
|  | 	      'RC': -3, | |||
|  | 	      'rc': -3, | |||
|  | 	      '#': -2, | |||
|  | 	      'p': 1, | |||
|  | 	      'pl': 1 | |||
|  | 	    }, | |||
|  | 	    // This function will be called to prepare each version argument.
 | |||
|  | 	    // It replaces every _, -, and + with a dot.
 | |||
|  | 	    // It surrounds any nonsequence of numbers/dots with dots.
 | |||
|  | 	    // It replaces sequences of dots with a single dot.
 | |||
|  | 	    //    version_compare('4..0', '4.0') == 0
 | |||
|  | 	    // Important: A string of 0 length needs to be converted into a value
 | |||
|  | 	    // even less than an unexisting value in vm (-7), hence [-8].
 | |||
|  | 	    // It's also important to not strip spaces because of this.
 | |||
|  | 	    //   version_compare('', ' ') == 1
 | |||
|  | 	    prepVersion = function (v) { | |||
|  | 	      v = ('' + v).replace(/[_\-+]/g, '.'); | |||
|  | 	      v = v.replace(/([^.\d]+)/g, '.$1.').replace(/\.{2,}/g, '.'); | |||
|  | 	      return (!v.length ? [-8] : v.split('.')); | |||
|  | 	    }, | |||
|  | 	    // This converts a version component to a number.
 | |||
|  | 	    // Empty component becomes 0.
 | |||
|  | 	    // Non-numerical component becomes a negative number.
 | |||
|  | 	    // Numerical component becomes itself as an integer.
 | |||
|  | 	    numVersion = function (v) { | |||
|  | 	      return !v ? 0 : (isNaN(v) ? vm[v] || -7 : parseInt(v, 10)); | |||
|  | 	    }; | |||
|  | 
 | |||
|  | 	  v1 = prepVersion(v1); | |||
|  | 	  v2 = prepVersion(v2); | |||
|  | 	  x = Math.max(v1.length, v2.length); | |||
|  | 	  for (i = 0; i < x; i++) { | |||
|  | 	    if (v1[i] == v2[i]) { | |||
|  | 	      continue; | |||
|  | 	    } | |||
|  | 	    v1[i] = numVersion(v1[i]); | |||
|  | 	    v2[i] = numVersion(v2[i]); | |||
|  | 	    if (v1[i] < v2[i]) { | |||
|  | 	      compare = -1; | |||
|  | 	      break; | |||
|  | 	    } else if (v1[i] > v2[i]) { | |||
|  | 	      compare = 1; | |||
|  | 	      break; | |||
|  | 	    } | |||
|  | 	  } | |||
|  | 	  if (!operator) { | |||
|  | 	    return compare; | |||
|  | 	  } | |||
|  | 
 | |||
|  | 	  // Important: operator is CASE-SENSITIVE.
 | |||
|  | 	  // "No operator" seems to be treated as "<."
 | |||
|  | 	  // Any other values seem to make the function return null.
 | |||
|  | 	  switch (operator) { | |||
|  | 	  case '>': | |||
|  | 	  case 'gt': | |||
|  | 	    return (compare > 0); | |||
|  | 	  case '>=': | |||
|  | 	  case 'ge': | |||
|  | 	    return (compare >= 0); | |||
|  | 	  case '<=': | |||
|  | 	  case 'le': | |||
|  | 	    return (compare <= 0); | |||
|  | 	  case '==': | |||
|  | 	  case '=': | |||
|  | 	  case 'eq': | |||
|  | 	    return (compare === 0); | |||
|  | 	  case '<>': | |||
|  | 	  case '!=': | |||
|  | 	  case 'ne': | |||
|  | 	    return (compare !== 0); | |||
|  | 	  case '': | |||
|  | 	  case '<': | |||
|  | 	  case 'lt': | |||
|  | 	    return (compare < 0); | |||
|  | 	  default: | |||
|  | 	    return null; | |||
|  | 	  } | |||
|  | 	} | |||
|  | 
 | |||
|  | 
 | |||
|  | 	var can = (function() { | |||
|  | 		var caps = { | |||
|  | 				define_property: (function() { | |||
|  | 					/* // currently too much extra code required, not exactly worth it | |||
|  | 					try { // as of IE8, getters/setters are supported only on DOM elements
 | |||
|  | 						var obj = {}; | |||
|  | 						if (Object.defineProperty) { | |||
|  | 							Object.defineProperty(obj, 'prop', { | |||
|  | 								enumerable: true, | |||
|  | 								configurable: true | |||
|  | 							}); | |||
|  | 							return true; | |||
|  | 						} | |||
|  | 					} catch(ex) {} | |||
|  | 
 | |||
|  | 					if (Object.prototype.__defineGetter__ && Object.prototype.__defineSetter__) { | |||
|  | 						return true; | |||
|  | 					}*/ | |||
|  | 					return false; | |||
|  | 				}()), | |||
|  | 
 | |||
|  | 				create_canvas: (function() { | |||
|  | 					// On the S60 and BB Storm, getContext exists, but always returns undefined
 | |||
|  | 					// so we actually have to call getContext() to verify
 | |||
|  | 					// github.com/Modernizr/Modernizr/issues/issue/97/
 | |||
|  | 					var el = document.createElement('canvas'); | |||
|  | 					return !!(el.getContext && el.getContext('2d')); | |||
|  | 				}()), | |||
|  | 
 | |||
|  | 				return_response_type: function(responseType) { | |||
|  | 					try { | |||
|  | 						if (Basic.inArray(responseType, ['', 'text', 'document']) !== -1) { | |||
|  | 							return true; | |||
|  | 						} else if (window.XMLHttpRequest) { | |||
|  | 							var xhr = new XMLHttpRequest(); | |||
|  | 							xhr.open('get', '/'); // otherwise Gecko throws an exception
 | |||
|  | 							if ('responseType' in xhr) { | |||
|  | 								xhr.responseType = responseType; | |||
|  | 								// as of 23.0.1271.64, Chrome switched from throwing exception to merely logging it to the console (why? o why?)
 | |||
|  | 								if (xhr.responseType !== responseType) { | |||
|  | 									return false; | |||
|  | 								} | |||
|  | 								return true; | |||
|  | 							} | |||
|  | 						} | |||
|  | 					} catch (ex) {} | |||
|  | 					return false; | |||
|  | 				}, | |||
|  | 
 | |||
|  | 				// ideas for this heavily come from Modernizr (http://modernizr.com/)
 | |||
|  | 				use_data_uri: (function() { | |||
|  | 					var du = new Image(); | |||
|  | 
 | |||
|  | 					du.onload = function() { | |||
|  | 						caps.use_data_uri = (du.width === 1 && du.height === 1); | |||
|  | 					}; | |||
|  | 					 | |||
|  | 					setTimeout(function() { | |||
|  | 						du.src = "data:image/gif;base64,R0lGODlhAQABAIAAAP8AAAAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=="; | |||
|  | 					}, 1); | |||
|  | 					return false; | |||
|  | 				}()), | |||
|  | 
 | |||
|  | 				use_data_uri_over32kb: function() { // IE8
 | |||
|  | 					return caps.use_data_uri && (Env.browser !== 'IE' || Env.version >= 9); | |||
|  | 				}, | |||
|  | 
 | |||
|  | 				use_data_uri_of: function(bytes) { | |||
|  | 					return (caps.use_data_uri && bytes < 33000 || caps.use_data_uri_over32kb()); | |||
|  | 				}, | |||
|  | 
 | |||
|  | 				use_fileinput: function() { | |||
|  | 					if (navigator.userAgent.match(/(Android (1.0|1.1|1.5|1.6|2.0|2.1))|(Windows Phone (OS 7|8.0))|(XBLWP)|(ZuneWP)|(w(eb)?OSBrowser)|(webOS)|(Kindle\/(1.0|2.0|2.5|3.0))/)) { | |||
|  | 						return false; | |||
|  | 					} | |||
|  | 
 | |||
|  | 					var el = document.createElement('input'); | |||
|  | 					el.setAttribute('type', 'file'); | |||
|  | 					return !el.disabled; | |||
|  | 				} | |||
|  | 			}; | |||
|  | 
 | |||
|  | 		return function(cap) { | |||
|  | 			var args = [].slice.call(arguments); | |||
|  | 			args.shift(); // shift of cap
 | |||
|  | 			return Basic.typeOf(caps[cap]) === 'function' ? caps[cap].apply(this, args) : !!caps[cap]; | |||
|  | 		}; | |||
|  | 	}()); | |||
|  | 
 | |||
|  | 
 | |||
|  | 	var uaResult = new UAParser().getResult(); | |||
|  | 
 | |||
|  | 
 | |||
|  | 	var Env = { | |||
|  | 		can: can, | |||
|  | 
 | |||
|  | 		uaParser: UAParser, | |||
|  | 		 | |||
|  | 		browser: uaResult.browser.name, | |||
|  | 		version: uaResult.browser.version, | |||
|  | 		os: uaResult.os.name, // everybody intuitively types it in a lowercase for some reason
 | |||
|  | 		osVersion: uaResult.os.version, | |||
|  | 
 | |||
|  | 		verComp: version_compare, | |||
|  | 
 | |||
|  | 		global_event_dispatcher: "moxie.core.EventTarget.instance.dispatchEvent" | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	// for backward compatibility
 | |||
|  | 	// @deprecated Use `Env.os` instead
 | |||
|  | 	Env.OS = Env.os; | |||
|  | 
 | |||
|  | 	if (MXI_DEBUG) { | |||
|  | 		Env.debug = { | |||
|  | 			runtime: true, | |||
|  | 			events: false | |||
|  | 		}; | |||
|  | 
 | |||
|  | 		Env.log = function() { | |||
|  | 			 | |||
|  | 			function logObj(data) { | |||
|  | 				// TODO: this should recursively print out the object in a pretty way
 | |||
|  | 				console.appendChild(document.createTextNode(data + "\n")); | |||
|  | 			} | |||
|  | 
 | |||
|  | 			var data = arguments[0]; | |||
|  | 
 | |||
|  | 			if (Basic.typeOf(data) === 'string') { | |||
|  | 				data = Basic.sprintf.apply(this, arguments); | |||
|  | 			} | |||
|  | 
 | |||
|  | 			if (window && window.console && window.console.log) { | |||
|  | 				window.console.log(data); | |||
|  | 			} else if (document) { | |||
|  | 				var console = document.getElementById('moxie-console'); | |||
|  | 				if (!console) { | |||
|  | 					console = document.createElement('pre'); | |||
|  | 					console.id = 'moxie-console'; | |||
|  | 					//console.style.display = 'none';
 | |||
|  | 					document.body.appendChild(console); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				if (Basic.inArray(Basic.typeOf(data), ['object', 'array']) !== -1) { | |||
|  | 					logObj(data); | |||
|  | 				} else { | |||
|  | 					console.appendChild(document.createTextNode(data + "\n")); | |||
|  | 				} | |||
|  | 			} | |||
|  | 		}; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return Env; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/core/I18n.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * I18n.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define("moxie/core/I18n", [ | |||
|  | 	"moxie/core/utils/Basic" | |||
|  | ], function(Basic) { | |||
|  | 	var i18n = {}; | |||
|  | 
 | |||
|  | 	return { | |||
|  | 		/** | |||
|  | 		 * Extends the language pack object with new items. | |||
|  | 		 * | |||
|  | 		 * @param {Object} pack Language pack items to add. | |||
|  | 		 * @return {Object} Extended language pack object. | |||
|  | 		 */ | |||
|  | 		addI18n: function(pack) { | |||
|  | 			return Basic.extend(i18n, pack); | |||
|  | 		}, | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		 * Translates the specified string by checking for the english string in the language pack lookup. | |||
|  | 		 * | |||
|  | 		 * @param {String} str String to look for. | |||
|  | 		 * @return {String} Translated string or the input string if it wasn't found. | |||
|  | 		 */ | |||
|  | 		translate: function(str) { | |||
|  | 			return i18n[str] || str; | |||
|  | 		}, | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		 * Shortcut for translate function | |||
|  | 		 * | |||
|  | 		 * @param {String} str String to look for. | |||
|  | 		 * @return {String} Translated string or the input string if it wasn't found. | |||
|  | 		 */ | |||
|  | 		_: function(str) { | |||
|  | 			return this.translate(str); | |||
|  | 		}, | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		 * Pseudo sprintf implementation - simple way to replace tokens with specified values. | |||
|  | 		 * | |||
|  | 		 * @param {String} str String with tokens | |||
|  | 		 * @return {String} String with replaced tokens | |||
|  | 		 */ | |||
|  | 		sprintf: function(str) { | |||
|  | 			var args = [].slice.call(arguments, 1); | |||
|  | 
 | |||
|  | 			return str.replace(/%[a-z]/g, function() { | |||
|  | 				var value = args.shift(); | |||
|  | 				return Basic.typeOf(value) !== 'undefined' ? value : ''; | |||
|  | 			}); | |||
|  | 		} | |||
|  | 	}; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/core/utils/Mime.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * Mime.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define("moxie/core/utils/Mime", [ | |||
|  | 	"moxie/core/utils/Basic", | |||
|  | 	"moxie/core/I18n" | |||
|  | ], function(Basic, I18n) { | |||
|  | 	 | |||
|  | 	var mimeData = "" + | |||
|  | 		"application/msword,doc dot," + | |||
|  | 		"application/pdf,pdf," + | |||
|  | 		"application/pgp-signature,pgp," + | |||
|  | 		"application/postscript,ps ai eps," + | |||
|  | 		"application/rtf,rtf," + | |||
|  | 		"application/vnd.ms-excel,xls xlb," + | |||
|  | 		"application/vnd.ms-powerpoint,ppt pps pot," + | |||
|  | 		"application/zip,zip," + | |||
|  | 		"application/x-shockwave-flash,swf swfl," + | |||
|  | 		"application/vnd.openxmlformats-officedocument.wordprocessingml.document,docx," + | |||
|  | 		"application/vnd.openxmlformats-officedocument.wordprocessingml.template,dotx," + | |||
|  | 		"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,xlsx," + | |||
|  | 		"application/vnd.openxmlformats-officedocument.presentationml.presentation,pptx," + | |||
|  | 		"application/vnd.openxmlformats-officedocument.presentationml.template,potx," + | |||
|  | 		"application/vnd.openxmlformats-officedocument.presentationml.slideshow,ppsx," + | |||
|  | 		"application/x-javascript,js," + | |||
|  | 		"application/json,json," + | |||
|  | 		"audio/mpeg,mp3 mpga mpega mp2," + | |||
|  | 		"audio/x-wav,wav," + | |||
|  | 		"audio/x-m4a,m4a," + | |||
|  | 		"audio/ogg,oga ogg," + | |||
|  | 		"audio/aiff,aiff aif," + | |||
|  | 		"audio/flac,flac," + | |||
|  | 		"audio/aac,aac," + | |||
|  | 		"audio/ac3,ac3," + | |||
|  | 		"audio/x-ms-wma,wma," + | |||
|  | 		"image/bmp,bmp," + | |||
|  | 		"image/gif,gif," + | |||
|  | 		"image/jpeg,jpg jpeg jpe," + | |||
|  | 		"image/photoshop,psd," + | |||
|  | 		"image/png,png," + | |||
|  | 		"image/svg+xml,svg svgz," + | |||
|  | 		"image/tiff,tiff tif," + | |||
|  | 		"text/plain,asc txt text diff log," + | |||
|  | 		"text/html,htm html xhtml," + | |||
|  | 		"text/css,css," + | |||
|  | 		"text/csv,csv," + | |||
|  | 		"text/rtf,rtf," + | |||
|  | 		"video/mpeg,mpeg mpg mpe m2v," + | |||
|  | 		"video/quicktime,qt mov," + | |||
|  | 		"video/mp4,mp4," + | |||
|  | 		"video/x-m4v,m4v," + | |||
|  | 		"video/x-flv,flv," + | |||
|  | 		"video/x-ms-wmv,wmv," + | |||
|  | 		"video/avi,avi," + | |||
|  | 		"video/webm,webm," + | |||
|  | 		"video/3gpp,3gpp 3gp," + | |||
|  | 		"video/3gpp2,3g2," + | |||
|  | 		"video/vnd.rn-realvideo,rv," + | |||
|  | 		"video/ogg,ogv," +  | |||
|  | 		"video/x-matroska,mkv," + | |||
|  | 		"application/vnd.oasis.opendocument.formula-template,otf," + | |||
|  | 		"application/octet-stream,exe"; | |||
|  | 	 | |||
|  | 	 | |||
|  | 	var Mime = { | |||
|  | 
 | |||
|  | 		mimes: {}, | |||
|  | 
 | |||
|  | 		extensions: {}, | |||
|  | 
 | |||
|  | 		// Parses the default mime types string into a mimes and extensions lookup maps
 | |||
|  | 		addMimeType: function (mimeData) { | |||
|  | 			var items = mimeData.split(/,/), i, ii, ext; | |||
|  | 			 | |||
|  | 			for (i = 0; i < items.length; i += 2) { | |||
|  | 				ext = items[i + 1].split(/ /); | |||
|  | 
 | |||
|  | 				// extension to mime lookup
 | |||
|  | 				for (ii = 0; ii < ext.length; ii++) { | |||
|  | 					this.mimes[ext[ii]] = items[i]; | |||
|  | 				} | |||
|  | 				// mime to extension lookup
 | |||
|  | 				this.extensions[items[i]] = ext; | |||
|  | 			} | |||
|  | 		}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 		extList2mimes: function (filters, addMissingExtensions) { | |||
|  | 			var self = this, ext, i, ii, type, mimes = []; | |||
|  | 			 | |||
|  | 			// convert extensions to mime types list
 | |||
|  | 			for (i = 0; i < filters.length; i++) { | |||
|  | 				ext = filters[i].extensions.split(/\s*,\s*/); | |||
|  | 
 | |||
|  | 				for (ii = 0; ii < ext.length; ii++) { | |||
|  | 					 | |||
|  | 					// if there's an asterisk in the list, then accept attribute is not required
 | |||
|  | 					if (ext[ii] === '*') { | |||
|  | 						return []; | |||
|  | 					} | |||
|  | 
 | |||
|  | 					type = self.mimes[ext[ii]]; | |||
|  | 					if (type && Basic.inArray(type, mimes) === -1) { | |||
|  | 						mimes.push(type); | |||
|  | 					} | |||
|  | 
 | |||
|  | 					// future browsers should filter by extension, finally
 | |||
|  | 					if (addMissingExtensions && /^\w+$/.test(ext[ii])) { | |||
|  | 						mimes.push('.' + ext[ii]); | |||
|  | 					} else if (!type) { | |||
|  | 						// if we have no type in our map, then accept all
 | |||
|  | 						return []; | |||
|  | 					} | |||
|  | 				} | |||
|  | 			} | |||
|  | 			return mimes; | |||
|  | 		}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 		mimes2exts: function(mimes) { | |||
|  | 			var self = this, exts = []; | |||
|  | 			 | |||
|  | 			Basic.each(mimes, function(mime) { | |||
|  | 				if (mime === '*') { | |||
|  | 					exts = []; | |||
|  | 					return false; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// check if this thing looks like mime type
 | |||
|  | 				var m = mime.match(/^(\w+)\/(\*|\w+)$/); | |||
|  | 				if (m) { | |||
|  | 					if (m[2] === '*') {  | |||
|  | 						// wildcard mime type detected
 | |||
|  | 						Basic.each(self.extensions, function(arr, mime) { | |||
|  | 							if ((new RegExp('^' + m[1] + '/')).test(mime)) { | |||
|  | 								[].push.apply(exts, self.extensions[mime]); | |||
|  | 							} | |||
|  | 						}); | |||
|  | 					} else if (self.extensions[mime]) { | |||
|  | 						[].push.apply(exts, self.extensions[mime]); | |||
|  | 					} | |||
|  | 				} | |||
|  | 			}); | |||
|  | 			return exts; | |||
|  | 		}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 		mimes2extList: function(mimes) { | |||
|  | 			var accept = [], exts = []; | |||
|  | 
 | |||
|  | 			if (Basic.typeOf(mimes) === 'string') { | |||
|  | 				mimes = Basic.trim(mimes).split(/\s*,\s*/); | |||
|  | 			} | |||
|  | 
 | |||
|  | 			exts = this.mimes2exts(mimes); | |||
|  | 			 | |||
|  | 			accept.push({ | |||
|  | 				title: I18n.translate('Files'), | |||
|  | 				extensions: exts.length ? exts.join(',') : '*' | |||
|  | 			}); | |||
|  | 			 | |||
|  | 			// save original mimes string
 | |||
|  | 			accept.mimes = mimes; | |||
|  | 
 | |||
|  | 			return accept; | |||
|  | 		}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 		getFileExtension: function(fileName) { | |||
|  | 			var matches = fileName && fileName.match(/\.([^.]+)$/); | |||
|  | 			if (matches) { | |||
|  | 				return matches[1].toLowerCase(); | |||
|  | 			} | |||
|  | 			return ''; | |||
|  | 		}, | |||
|  | 
 | |||
|  | 		getFileMime: function(fileName) { | |||
|  | 			return this.mimes[this.getFileExtension(fileName)] || ''; | |||
|  | 		} | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	Mime.addMimeType(mimeData); | |||
|  | 
 | |||
|  | 	return Mime; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/core/utils/Dom.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * Dom.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define('moxie/core/utils/Dom', ['moxie/core/utils/Env'], function(Env) { | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Get DOM Element by it's id. | |||
|  | 
 | |||
|  | 	@method get | |||
|  | 	@for Utils | |||
|  | 	@param {String} id Identifier of the DOM Element | |||
|  | 	@return {DOMElement} | |||
|  | 	*/ | |||
|  | 	var get = function(id) { | |||
|  | 		if (typeof id !== 'string') { | |||
|  | 			return id; | |||
|  | 		} | |||
|  | 		return document.getElementById(id); | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	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 | |||
|  | 	*/ | |||
|  | 	var hasClass = function(obj, name) { | |||
|  | 		if (!obj.className) { | |||
|  | 			return false; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		var regExp = new RegExp("(^|\\s+)"+name+"(\\s+|$)"); | |||
|  | 		return regExp.test(obj.className); | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	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 | |||
|  | 	*/ | |||
|  | 	var addClass = function(obj, name) { | |||
|  | 		if (!hasClass(obj, name)) { | |||
|  | 			obj.className = !obj.className ? name : obj.className.replace(/\s+$/, '') + ' ' + name; | |||
|  | 		} | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	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 | |||
|  | 	*/ | |||
|  | 	var removeClass = function(obj, name) { | |||
|  | 		if (obj.className) { | |||
|  | 			var regExp = new RegExp("(^|\\s+)"+name+"(\\s+|$)"); | |||
|  | 			obj.className = obj.className.replace(regExp, function($0, $1, $2) { | |||
|  | 				return $1 === ' ' && $2 === ' ' ? ' ' : ''; | |||
|  | 			}); | |||
|  | 		} | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	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 | |||
|  | 	*/ | |||
|  | 	var getStyle = function(obj, name) { | |||
|  | 		if (obj.currentStyle) { | |||
|  | 			return obj.currentStyle[name]; | |||
|  | 		} else if (window.getComputedStyle) { | |||
|  | 			return window.getComputedStyle(obj, null)[name]; | |||
|  | 		} | |||
|  | 	}; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	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. | |||
|  | 	*/ | |||
|  | 	var getPos = function(node, root) { | |||
|  | 		var x = 0, y = 0, parent, doc = document, nodeRect, rootRect; | |||
|  | 
 | |||
|  | 		node = node; | |||
|  | 		root = root || doc.body; | |||
|  | 
 | |||
|  | 		// Returns the x, y cordinate for an element on IE 6 and IE 7
 | |||
|  | 		function getIEPos(node) { | |||
|  | 			var bodyElm, rect, x = 0, y = 0; | |||
|  | 
 | |||
|  | 			if (node) { | |||
|  | 				rect = node.getBoundingClientRect(); | |||
|  | 				bodyElm = doc.compatMode === "CSS1Compat" ? doc.documentElement : doc.body; | |||
|  | 				x = rect.left + bodyElm.scrollLeft; | |||
|  | 				y = rect.top + bodyElm.scrollTop; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			return { | |||
|  | 				x : x, | |||
|  | 				y : y | |||
|  | 			}; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// Use getBoundingClientRect on IE 6 and IE 7 but not on IE 8 in standards mode
 | |||
|  | 		if (node && node.getBoundingClientRect && Env.browser === 'IE' && (!doc.documentMode || doc.documentMode < 8)) { | |||
|  | 			nodeRect = getIEPos(node); | |||
|  | 			rootRect = getIEPos(root); | |||
|  | 
 | |||
|  | 			return { | |||
|  | 				x : nodeRect.x - rootRect.x, | |||
|  | 				y : nodeRect.y - rootRect.y | |||
|  | 			}; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		parent = node; | |||
|  | 		while (parent && parent != root && parent.nodeType) { | |||
|  | 			x += parent.offsetLeft || 0; | |||
|  | 			y += parent.offsetTop || 0; | |||
|  | 			parent = parent.offsetParent; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		parent = node.parentNode; | |||
|  | 		while (parent && parent != root && parent.nodeType) { | |||
|  | 			x -= parent.scrollLeft || 0; | |||
|  | 			y -= parent.scrollTop || 0; | |||
|  | 			parent = parent.parentNode; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		return { | |||
|  | 			x : x, | |||
|  | 			y : y | |||
|  | 		}; | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	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. | |||
|  | 	*/ | |||
|  | 	var getSize = function(node) { | |||
|  | 		return { | |||
|  | 			w : node.offsetWidth || node.clientWidth, | |||
|  | 			h : node.offsetHeight || node.clientHeight | |||
|  | 		}; | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	return { | |||
|  | 		get: get, | |||
|  | 		hasClass: hasClass, | |||
|  | 		addClass: addClass, | |||
|  | 		removeClass: removeClass, | |||
|  | 		getStyle: getStyle, | |||
|  | 		getPos: getPos, | |||
|  | 		getSize: getSize | |||
|  | 	}; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/core/Exceptions.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * Exceptions.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define('moxie/core/Exceptions', [ | |||
|  | 	'moxie/core/utils/Basic' | |||
|  | ], function(Basic) { | |||
|  | 	function _findKey(obj, value) { | |||
|  | 		var key; | |||
|  | 		for (key in obj) { | |||
|  | 			if (obj[key] === value) { | |||
|  | 				return key; | |||
|  | 			} | |||
|  | 		} | |||
|  | 		return null; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return { | |||
|  | 		RuntimeError: (function() { | |||
|  | 			var namecodes = { | |||
|  | 				NOT_INIT_ERR: 1, | |||
|  | 				NOT_SUPPORTED_ERR: 9, | |||
|  | 				JS_ERR: 4 | |||
|  | 			}; | |||
|  | 
 | |||
|  | 			function RuntimeError(code) { | |||
|  | 				this.code = code; | |||
|  | 				this.name = _findKey(namecodes, code); | |||
|  | 				this.message = this.name + ": RuntimeError " + this.code; | |||
|  | 			} | |||
|  | 			 | |||
|  | 			Basic.extend(RuntimeError, namecodes); | |||
|  | 			RuntimeError.prototype = Error.prototype; | |||
|  | 			return RuntimeError; | |||
|  | 		}()), | |||
|  | 		 | |||
|  | 		OperationNotAllowedException: (function() { | |||
|  | 			 | |||
|  | 			function OperationNotAllowedException(code) { | |||
|  | 				this.code = code; | |||
|  | 				this.name = 'OperationNotAllowedException'; | |||
|  | 			} | |||
|  | 			 | |||
|  | 			Basic.extend(OperationNotAllowedException, { | |||
|  | 				NOT_ALLOWED_ERR: 1 | |||
|  | 			}); | |||
|  | 			 | |||
|  | 			OperationNotAllowedException.prototype = Error.prototype; | |||
|  | 			 | |||
|  | 			return OperationNotAllowedException; | |||
|  | 		}()), | |||
|  | 
 | |||
|  | 		ImageError: (function() { | |||
|  | 			var namecodes = { | |||
|  | 				WRONG_FORMAT: 1, | |||
|  | 				MAX_RESOLUTION_ERR: 2, | |||
|  | 				INVALID_META_ERR: 3 | |||
|  | 			}; | |||
|  | 
 | |||
|  | 			function ImageError(code) { | |||
|  | 				this.code = code; | |||
|  | 				this.name = _findKey(namecodes, code); | |||
|  | 				this.message = this.name + ": ImageError " + this.code; | |||
|  | 			} | |||
|  | 			 | |||
|  | 			Basic.extend(ImageError, namecodes); | |||
|  | 			ImageError.prototype = Error.prototype; | |||
|  | 
 | |||
|  | 			return ImageError; | |||
|  | 		}()), | |||
|  | 
 | |||
|  | 		FileException: (function() { | |||
|  | 			var namecodes = { | |||
|  | 				NOT_FOUND_ERR: 1, | |||
|  | 				SECURITY_ERR: 2, | |||
|  | 				ABORT_ERR: 3, | |||
|  | 				NOT_READABLE_ERR: 4, | |||
|  | 				ENCODING_ERR: 5, | |||
|  | 				NO_MODIFICATION_ALLOWED_ERR: 6, | |||
|  | 				INVALID_STATE_ERR: 7, | |||
|  | 				SYNTAX_ERR: 8 | |||
|  | 			}; | |||
|  | 
 | |||
|  | 			function FileException(code) { | |||
|  | 				this.code = code; | |||
|  | 				this.name = _findKey(namecodes, code); | |||
|  | 				this.message = this.name + ": FileException " + this.code; | |||
|  | 			} | |||
|  | 			 | |||
|  | 			Basic.extend(FileException, namecodes); | |||
|  | 			FileException.prototype = Error.prototype; | |||
|  | 			return FileException; | |||
|  | 		}()), | |||
|  | 		 | |||
|  | 		DOMException: (function() { | |||
|  | 			var namecodes = { | |||
|  | 				INDEX_SIZE_ERR: 1, | |||
|  | 				DOMSTRING_SIZE_ERR: 2, | |||
|  | 				HIERARCHY_REQUEST_ERR: 3, | |||
|  | 				WRONG_DOCUMENT_ERR: 4, | |||
|  | 				INVALID_CHARACTER_ERR: 5, | |||
|  | 				NO_DATA_ALLOWED_ERR: 6, | |||
|  | 				NO_MODIFICATION_ALLOWED_ERR: 7, | |||
|  | 				NOT_FOUND_ERR: 8, | |||
|  | 				NOT_SUPPORTED_ERR: 9, | |||
|  | 				INUSE_ATTRIBUTE_ERR: 10, | |||
|  | 				INVALID_STATE_ERR: 11, | |||
|  | 				SYNTAX_ERR: 12, | |||
|  | 				INVALID_MODIFICATION_ERR: 13, | |||
|  | 				NAMESPACE_ERR: 14, | |||
|  | 				INVALID_ACCESS_ERR: 15, | |||
|  | 				VALIDATION_ERR: 16, | |||
|  | 				TYPE_MISMATCH_ERR: 17, | |||
|  | 				SECURITY_ERR: 18, | |||
|  | 				NETWORK_ERR: 19, | |||
|  | 				ABORT_ERR: 20, | |||
|  | 				URL_MISMATCH_ERR: 21, | |||
|  | 				QUOTA_EXCEEDED_ERR: 22, | |||
|  | 				TIMEOUT_ERR: 23, | |||
|  | 				INVALID_NODE_TYPE_ERR: 24, | |||
|  | 				DATA_CLONE_ERR: 25 | |||
|  | 			}; | |||
|  | 
 | |||
|  | 			function DOMException(code) { | |||
|  | 				this.code = code; | |||
|  | 				this.name = _findKey(namecodes, code); | |||
|  | 				this.message = this.name + ": DOMException " + this.code; | |||
|  | 			} | |||
|  | 			 | |||
|  | 			Basic.extend(DOMException, namecodes); | |||
|  | 			DOMException.prototype = Error.prototype; | |||
|  | 			return DOMException; | |||
|  | 		}()), | |||
|  | 		 | |||
|  | 		EventException: (function() { | |||
|  | 			function EventException(code) { | |||
|  | 				this.code = code; | |||
|  | 				this.name = 'EventException'; | |||
|  | 			} | |||
|  | 			 | |||
|  | 			Basic.extend(EventException, { | |||
|  | 				UNSPECIFIED_EVENT_TYPE_ERR: 0 | |||
|  | 			}); | |||
|  | 			 | |||
|  | 			EventException.prototype = Error.prototype; | |||
|  | 			 | |||
|  | 			return EventException; | |||
|  | 		}()) | |||
|  | 	}; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/core/EventTarget.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * EventTarget.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define('moxie/core/EventTarget', [ | |||
|  | 	'moxie/core/utils/Env', | |||
|  | 	'moxie/core/Exceptions', | |||
|  | 	'moxie/core/utils/Basic' | |||
|  | ], function(Env, x, Basic) { | |||
|  | 	/** | |||
|  | 	Parent object for all event dispatching components and objects | |||
|  | 
 | |||
|  | 	@class EventTarget | |||
|  | 	@constructor EventTarget | |||
|  | 	*/ | |||
|  | 	function EventTarget() { | |||
|  | 		// hash of event listeners by object uid
 | |||
|  | 		var eventpool = {}; | |||
|  | 				 | |||
|  | 		Basic.extend(this, { | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Unique id of the event dispatcher, usually overriden by children | |||
|  | 
 | |||
|  | 			@property uid | |||
|  | 			@type String | |||
|  | 			*/ | |||
|  | 			uid: null, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Can be called from within a child  in order to acquire uniqie id in automated manner | |||
|  | 
 | |||
|  | 			@method init | |||
|  | 			*/ | |||
|  | 			init: function() { | |||
|  | 				if (!this.uid) { | |||
|  | 					this.uid = Basic.guid('uid_'); | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Register a handler to a specific event dispatched by the object | |||
|  | 
 | |||
|  | 			@method addEventListener | |||
|  | 			@param {String} type Type or basically a name of the event to subscribe to | |||
|  | 			@param {Function} fn Callback function that will be called when event happens | |||
|  | 			@param {Number} [priority=0] Priority of the event handler - handlers with higher priorities will be called first | |||
|  | 			@param {Object} [scope=this] A scope to invoke event handler in | |||
|  | 			*/ | |||
|  | 			addEventListener: function(type, fn, priority, scope) { | |||
|  | 				var self = this, list; | |||
|  | 
 | |||
|  | 				// without uid no event handlers can be added, so make sure we got one
 | |||
|  | 				if (!this.hasOwnProperty('uid')) { | |||
|  | 					this.uid = Basic.guid('uid_'); | |||
|  | 				} | |||
|  | 				 | |||
|  | 				type = Basic.trim(type); | |||
|  | 				 | |||
|  | 				if (/\s/.test(type)) { | |||
|  | 					// multiple event types were passed for one handler
 | |||
|  | 					Basic.each(type.split(/\s+/), function(type) { | |||
|  | 						self.addEventListener(type, fn, priority, scope); | |||
|  | 					}); | |||
|  | 					return; | |||
|  | 				} | |||
|  | 				 | |||
|  | 				type = type.toLowerCase(); | |||
|  | 				priority = parseInt(priority, 10) || 0; | |||
|  | 				 | |||
|  | 				list = eventpool[this.uid] && eventpool[this.uid][type] || []; | |||
|  | 				list.push({fn : fn, priority : priority, scope : scope || this}); | |||
|  | 				 | |||
|  | 				if (!eventpool[this.uid]) { | |||
|  | 					eventpool[this.uid] = {}; | |||
|  | 				} | |||
|  | 				eventpool[this.uid][type] = list; | |||
|  | 			}, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Check if any handlers were registered to the specified event | |||
|  | 
 | |||
|  | 			@method hasEventListener | |||
|  | 			@param {String} type Type or basically a name of the event to check | |||
|  | 			@return {Mixed} Returns a handler if it was found and false, if - not | |||
|  | 			*/ | |||
|  | 			hasEventListener: function(type) { | |||
|  | 				var list = type ? eventpool[this.uid] && eventpool[this.uid][type] : eventpool[this.uid]; | |||
|  | 				return list ? list : false; | |||
|  | 			}, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Unregister the handler from the event, or if former was not specified - unregister all handlers | |||
|  | 
 | |||
|  | 			@method removeEventListener | |||
|  | 			@param {String} type Type or basically a name of the event | |||
|  | 			@param {Function} [fn] Handler to unregister | |||
|  | 			*/ | |||
|  | 			removeEventListener: function(type, fn) { | |||
|  | 				type = type.toLowerCase(); | |||
|  | 	 | |||
|  | 				var list = eventpool[this.uid] && eventpool[this.uid][type], i; | |||
|  | 	 | |||
|  | 				if (list) { | |||
|  | 					if (fn) { | |||
|  | 						for (i = list.length - 1; i >= 0; i--) { | |||
|  | 							if (list[i].fn === fn) { | |||
|  | 								list.splice(i, 1); | |||
|  | 								break; | |||
|  | 							} | |||
|  | 						} | |||
|  | 					} else { | |||
|  | 						list = []; | |||
|  | 					} | |||
|  | 	 | |||
|  | 					// delete event list if it has become empty
 | |||
|  | 					if (!list.length) { | |||
|  | 						delete eventpool[this.uid][type]; | |||
|  | 						 | |||
|  | 						// and object specific entry in a hash if it has no more listeners attached
 | |||
|  | 						if (Basic.isEmptyObj(eventpool[this.uid])) { | |||
|  | 							delete eventpool[this.uid]; | |||
|  | 						} | |||
|  | 					} | |||
|  | 				} | |||
|  | 			}, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Remove all event handlers from the object | |||
|  | 
 | |||
|  | 			@method removeAllEventListeners | |||
|  | 			*/ | |||
|  | 			removeAllEventListeners: function() { | |||
|  | 				if (eventpool[this.uid]) { | |||
|  | 					delete eventpool[this.uid]; | |||
|  | 				} | |||
|  | 			}, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Dispatch the event | |||
|  | 
 | |||
|  | 			@method dispatchEvent | |||
|  | 			@param {String/Object} Type of event or event object to dispatch | |||
|  | 			@param {Mixed} [...] Variable number of arguments to be passed to a handlers | |||
|  | 			@return {Boolean} true by default and false if any handler returned false | |||
|  | 			*/ | |||
|  | 			dispatchEvent: function(type) { | |||
|  | 				var uid, list, args, tmpEvt, evt = {}, result = true, undef; | |||
|  | 				 | |||
|  | 				if (Basic.typeOf(type) !== 'string') { | |||
|  | 					// we can't use original object directly (because of Silverlight)
 | |||
|  | 					tmpEvt = type; | |||
|  | 
 | |||
|  | 					if (Basic.typeOf(tmpEvt.type) === 'string') { | |||
|  | 						type = tmpEvt.type; | |||
|  | 
 | |||
|  | 						if (tmpEvt.total !== undef && tmpEvt.loaded !== undef) { // progress event
 | |||
|  | 							evt.total = tmpEvt.total; | |||
|  | 							evt.loaded = tmpEvt.loaded; | |||
|  | 						} | |||
|  | 						evt.async = tmpEvt.async || false; | |||
|  | 					} else { | |||
|  | 						throw new x.EventException(x.EventException.UNSPECIFIED_EVENT_TYPE_ERR); | |||
|  | 					} | |||
|  | 				} | |||
|  | 				 | |||
|  | 				// check if event is meant to be dispatched on an object having specific uid
 | |||
|  | 				if (type.indexOf('::') !== -1) { | |||
|  | 					(function(arr) { | |||
|  | 						uid = arr[0]; | |||
|  | 						type = arr[1]; | |||
|  | 					}(type.split('::'))); | |||
|  | 				} else { | |||
|  | 					uid = this.uid; | |||
|  | 				} | |||
|  | 				 | |||
|  | 				type = type.toLowerCase(); | |||
|  | 								 | |||
|  | 				list = eventpool[uid] && eventpool[uid][type]; | |||
|  | 
 | |||
|  | 				if (list) { | |||
|  | 					// sort event list by prority
 | |||
|  | 					list.sort(function(a, b) { return b.priority - a.priority; }); | |||
|  | 					 | |||
|  | 					args = [].slice.call(arguments); | |||
|  | 					 | |||
|  | 					// first argument will be pseudo-event object
 | |||
|  | 					args.shift(); | |||
|  | 					evt.type = type; | |||
|  | 					args.unshift(evt); | |||
|  | 
 | |||
|  | 					if (MXI_DEBUG && Env.debug.events) { | |||
|  | 						Env.log("Event '%s' fired on %u", evt.type, uid);	 | |||
|  | 					} | |||
|  | 
 | |||
|  | 					// Dispatch event to all listeners
 | |||
|  | 					var queue = []; | |||
|  | 					Basic.each(list, function(handler) { | |||
|  | 						// explicitly set the target, otherwise events fired from shims do not get it
 | |||
|  | 						args[0].target = handler.scope; | |||
|  | 						// if event is marked as async, detach the handler
 | |||
|  | 						if (evt.async) { | |||
|  | 							queue.push(function(cb) { | |||
|  | 								setTimeout(function() { | |||
|  | 									cb(handler.fn.apply(handler.scope, args) === false); | |||
|  | 								}, 1); | |||
|  | 							}); | |||
|  | 						} else { | |||
|  | 							queue.push(function(cb) { | |||
|  | 								cb(handler.fn.apply(handler.scope, args) === false); // if handler returns false stop propagation
 | |||
|  | 							}); | |||
|  | 						} | |||
|  | 					}); | |||
|  | 					if (queue.length) { | |||
|  | 						Basic.inSeries(queue, function(err) { | |||
|  | 							result = !err; | |||
|  | 						}); | |||
|  | 					} | |||
|  | 				} | |||
|  | 				return result; | |||
|  | 			}, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Alias for addEventListener | |||
|  | 
 | |||
|  | 			@method bind | |||
|  | 			@protected | |||
|  | 			*/ | |||
|  | 			bind: function() { | |||
|  | 				this.addEventListener.apply(this, arguments); | |||
|  | 			}, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Alias for removeEventListener | |||
|  | 
 | |||
|  | 			@method unbind | |||
|  | 			@protected | |||
|  | 			*/ | |||
|  | 			unbind: function() { | |||
|  | 				this.removeEventListener.apply(this, arguments); | |||
|  | 			}, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Alias for removeAllEventListeners | |||
|  | 
 | |||
|  | 			@method unbindAll | |||
|  | 			@protected | |||
|  | 			*/ | |||
|  | 			unbindAll: function() { | |||
|  | 				this.removeAllEventListeners.apply(this, arguments); | |||
|  | 			}, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Alias for dispatchEvent | |||
|  | 
 | |||
|  | 			@method trigger | |||
|  | 			@protected | |||
|  | 			*/ | |||
|  | 			trigger: function() { | |||
|  | 				return this.dispatchEvent.apply(this, arguments); | |||
|  | 			}, | |||
|  | 			 | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Handle properties of on[event] type. | |||
|  | 
 | |||
|  | 			@method handleEventProps | |||
|  | 			@private | |||
|  | 			*/ | |||
|  | 			handleEventProps: function(dispatches) { | |||
|  | 				var self = this; | |||
|  | 
 | |||
|  | 				this.bind(dispatches.join(' '), function(e) { | |||
|  | 					var prop = 'on' + e.type.toLowerCase(); | |||
|  | 					if (Basic.typeOf(this[prop]) === 'function') { | |||
|  | 						this[prop].apply(this, arguments); | |||
|  | 					} | |||
|  | 				}); | |||
|  | 
 | |||
|  | 				// object must have defined event properties, even if it doesn't make use of them
 | |||
|  | 				Basic.each(dispatches, function(prop) { | |||
|  | 					prop = 'on' + prop.toLowerCase(prop); | |||
|  | 					if (Basic.typeOf(self[prop]) === 'undefined') { | |||
|  | 						self[prop] = null;  | |||
|  | 					} | |||
|  | 				}); | |||
|  | 			} | |||
|  | 			 | |||
|  | 		}); | |||
|  | 	} | |||
|  | 
 | |||
|  | 	EventTarget.instance = new EventTarget();  | |||
|  | 
 | |||
|  | 	return EventTarget; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/Runtime.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * Runtime.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define('moxie/runtime/Runtime', [ | |||
|  | 	"moxie/core/utils/Env", | |||
|  | 	"moxie/core/utils/Basic", | |||
|  | 	"moxie/core/utils/Dom", | |||
|  | 	"moxie/core/EventTarget" | |||
|  | ], function(Env, Basic, Dom, EventTarget) { | |||
|  | 	var runtimeConstructors = {}, runtimes = {}; | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Common set of methods and properties for every runtime instance | |||
|  | 
 | |||
|  | 	@class Runtime | |||
|  | 
 | |||
|  | 	@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 | |||
|  | 	*/ | |||
|  | 	function Runtime(options, type, caps, modeCaps, preferredMode) { | |||
|  | 		/** | |||
|  | 		Dispatched when runtime is initialized and ready. | |||
|  | 		Results in RuntimeInit on a connected component. | |||
|  | 
 | |||
|  | 		@event Init | |||
|  | 		*/ | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		Dispatched when runtime fails to initialize. | |||
|  | 		Results in RuntimeError on a connected component. | |||
|  | 
 | |||
|  | 		@event Error | |||
|  | 		*/ | |||
|  | 
 | |||
|  | 		var self = this | |||
|  | 		, _shim | |||
|  | 		, _uid = Basic.guid(type + '_') | |||
|  | 		, defaultMode = preferredMode || 'browser' | |||
|  | 		; | |||
|  | 
 | |||
|  | 		options = options || {}; | |||
|  | 
 | |||
|  | 		// register runtime in private hash
 | |||
|  | 		runtimes[_uid] = this; | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		Default set of capabilities, which can be redifined later by specific runtime | |||
|  | 
 | |||
|  | 		@private | |||
|  | 		@property caps | |||
|  | 		@type Object | |||
|  | 		*/ | |||
|  | 		caps = Basic.extend({ | |||
|  | 			// Runtime can: 
 | |||
|  | 			// provide access to raw binary data of the file
 | |||
|  | 			access_binary: false, | |||
|  | 			// provide access to raw binary data of the image (image extension is optional) 
 | |||
|  | 			access_image_binary: false, | |||
|  | 			// display binary data as thumbs for example
 | |||
|  | 			display_media: false, | |||
|  | 			// make cross-domain requests
 | |||
|  | 			do_cors: false, | |||
|  | 			// accept files dragged and dropped from the desktop
 | |||
|  | 			drag_and_drop: false, | |||
|  | 			// filter files in selection dialog by their extensions
 | |||
|  | 			filter_by_extension: true, | |||
|  | 			// resize image (and manipulate it raw data of any file in general)
 | |||
|  | 			resize_image: false, | |||
|  | 			// periodically report how many bytes of total in the file were uploaded (loaded)
 | |||
|  | 			report_upload_progress: false, | |||
|  | 			// provide access to the headers of http response 
 | |||
|  | 			return_response_headers: false, | |||
|  | 			// support response of specific type, which should be passed as an argument
 | |||
|  | 			// e.g. runtime.can('return_response_type', 'blob')
 | |||
|  | 			return_response_type: false, | |||
|  | 			// return http status code of the response
 | |||
|  | 			return_status_code: true, | |||
|  | 			// send custom http header with the request
 | |||
|  | 			send_custom_headers: false, | |||
|  | 			// pick up the files from a dialog
 | |||
|  | 			select_file: false, | |||
|  | 			// select whole folder in file browse dialog
 | |||
|  | 			select_folder: false, | |||
|  | 			// select multiple files at once in file browse dialog
 | |||
|  | 			select_multiple: true, | |||
|  | 			// send raw binary data, that is generated after image resizing or manipulation of other kind
 | |||
|  | 			send_binary_string: false, | |||
|  | 			// send cookies with http request and therefore retain session
 | |||
|  | 			send_browser_cookies: true, | |||
|  | 			// send data formatted as multipart/form-data
 | |||
|  | 			send_multipart: true, | |||
|  | 			// slice the file or blob to smaller parts
 | |||
|  | 			slice_blob: false, | |||
|  | 			// upload file without preloading it to memory, stream it out directly from disk
 | |||
|  | 			stream_upload: false, | |||
|  | 			// programmatically trigger file browse dialog
 | |||
|  | 			summon_file_dialog: false, | |||
|  | 			// upload file of specific size, size should be passed as argument
 | |||
|  | 			// e.g. runtime.can('upload_filesize', '500mb')
 | |||
|  | 			upload_filesize: true, | |||
|  | 			// initiate http request with specific http method, method should be passed as argument
 | |||
|  | 			// e.g. runtime.can('use_http_method', 'put')
 | |||
|  | 			use_http_method: true | |||
|  | 		}, caps); | |||
|  | 			 | |||
|  | 	 | |||
|  | 		// default to the mode that is compatible with preferred caps
 | |||
|  | 		if (options.preferred_caps) { | |||
|  | 			defaultMode = Runtime.getMode(modeCaps, options.preferred_caps, defaultMode); | |||
|  | 		} | |||
|  | 
 | |||
|  | 		if (MXI_DEBUG && Env.debug.runtime) { | |||
|  | 			Env.log("\tdefault mode: %s", defaultMode);	 | |||
|  | 		} | |||
|  | 		 | |||
|  | 		// small extension factory here (is meant to be extended with actual extensions constructors)
 | |||
|  | 		_shim = (function() { | |||
|  | 			var objpool = {}; | |||
|  | 			return { | |||
|  | 				exec: function(uid, comp, fn, args) { | |||
|  | 					if (_shim[comp]) { | |||
|  | 						if (!objpool[uid]) { | |||
|  | 							objpool[uid] = { | |||
|  | 								context: this, | |||
|  | 								instance: new _shim[comp]() | |||
|  | 							}; | |||
|  | 						} | |||
|  | 						if (objpool[uid].instance[fn]) { | |||
|  | 							return objpool[uid].instance[fn].apply(this, args); | |||
|  | 						} | |||
|  | 					} | |||
|  | 				}, | |||
|  | 
 | |||
|  | 				removeInstance: function(uid) { | |||
|  | 					delete objpool[uid]; | |||
|  | 				}, | |||
|  | 
 | |||
|  | 				removeAllInstances: function() { | |||
|  | 					var self = this; | |||
|  | 					Basic.each(objpool, function(obj, uid) { | |||
|  | 						if (Basic.typeOf(obj.instance.destroy) === 'function') { | |||
|  | 							obj.instance.destroy.call(obj.context); | |||
|  | 						} | |||
|  | 						self.removeInstance(uid); | |||
|  | 					}); | |||
|  | 				} | |||
|  | 			}; | |||
|  | 		}()); | |||
|  | 
 | |||
|  | 
 | |||
|  | 		// public methods
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			/** | |||
|  | 			Specifies whether runtime instance was initialized or not | |||
|  | 
 | |||
|  | 			@property initialized | |||
|  | 			@type {Boolean} | |||
|  | 			@default false | |||
|  | 			*/ | |||
|  | 			initialized: false, // shims require this flag to stop initialization retries
 | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Unique ID of the runtime | |||
|  | 
 | |||
|  | 			@property uid | |||
|  | 			@type {String} | |||
|  | 			*/ | |||
|  | 			uid: _uid, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Runtime type (e.g. flash, html5, etc) | |||
|  | 
 | |||
|  | 			@property type | |||
|  | 			@type {String} | |||
|  | 			*/ | |||
|  | 			type: type, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Runtime (not native one) may operate in browser or client mode. | |||
|  | 
 | |||
|  | 			@property mode | |||
|  | 			@private | |||
|  | 			@type {String|Boolean} current mode or false, if none possible | |||
|  | 			*/ | |||
|  | 			mode: Runtime.getMode(modeCaps, (options.required_caps), defaultMode), | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			id of the DOM container for the runtime (if available) | |||
|  | 
 | |||
|  | 			@property shimid | |||
|  | 			@type {String} | |||
|  | 			*/ | |||
|  | 			shimid: _uid + '_container', | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Number of connected clients. If equal to zero, runtime can be destroyed | |||
|  | 
 | |||
|  | 			@property clients | |||
|  | 			@type {Number} | |||
|  | 			*/ | |||
|  | 			clients: 0, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Runtime initialization options | |||
|  | 
 | |||
|  | 			@property options | |||
|  | 			@type {Object} | |||
|  | 			*/ | |||
|  | 			options: options, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Checks if the runtime has specific capability | |||
|  | 
 | |||
|  | 			@method can | |||
|  | 			@param {String} cap Name of capability to check | |||
|  | 			@param {Mixed} [value] If passed, capability should somehow correlate to the value | |||
|  | 			@param {Object} [refCaps] Set of capabilities to check the specified cap against (defaults to internal set) | |||
|  | 			@return {Boolean} true if runtime has such capability and false, if - not | |||
|  | 			*/ | |||
|  | 			can: function(cap, value) { | |||
|  | 				var refCaps = arguments[2] || caps; | |||
|  | 
 | |||
|  | 				// if cap var is a comma-separated list of caps, convert it to object (key/value)
 | |||
|  | 				if (Basic.typeOf(cap) === 'string' && Basic.typeOf(value) === 'undefined') { | |||
|  | 					cap = Runtime.parseCaps(cap); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				if (Basic.typeOf(cap) === 'object') { | |||
|  | 					for (var key in cap) { | |||
|  | 						if (!this.can(key, cap[key], refCaps)) { | |||
|  | 							return false; | |||
|  | 						} | |||
|  | 					} | |||
|  | 					return true; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// check the individual cap
 | |||
|  | 				if (Basic.typeOf(refCaps[cap]) === 'function') { | |||
|  | 					return refCaps[cap].call(this, value); | |||
|  | 				} else { | |||
|  | 					return (value === refCaps[cap]); | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Returns container for the runtime as DOM element | |||
|  | 
 | |||
|  | 			@method getShimContainer | |||
|  | 			@return {DOMElement} | |||
|  | 			*/ | |||
|  | 			getShimContainer: function() { | |||
|  | 				var container, shimContainer = Dom.get(this.shimid); | |||
|  | 
 | |||
|  | 				// if no container for shim, create one
 | |||
|  | 				if (!shimContainer) { | |||
|  | 					container = this.options.container ? Dom.get(this.options.container) : document.body; | |||
|  | 
 | |||
|  | 					// create shim container and insert it at an absolute position into the outer container
 | |||
|  | 					shimContainer = document.createElement('div'); | |||
|  | 					shimContainer.id = this.shimid; | |||
|  | 					shimContainer.className = 'moxie-shim moxie-shim-' + this.type; | |||
|  | 
 | |||
|  | 					Basic.extend(shimContainer.style, { | |||
|  | 						position: 'absolute', | |||
|  | 						top: '0px', | |||
|  | 						left: '0px', | |||
|  | 						width: '1px', | |||
|  | 						height: '1px', | |||
|  | 						overflow: 'hidden' | |||
|  | 					}); | |||
|  | 
 | |||
|  | 					container.appendChild(shimContainer); | |||
|  | 					container = null; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				return shimContainer; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Returns runtime as DOM element (if appropriate) | |||
|  | 
 | |||
|  | 			@method getShim | |||
|  | 			@return {DOMElement} | |||
|  | 			*/ | |||
|  | 			getShim: function() { | |||
|  | 				return _shim; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Invokes a method within the runtime itself (might differ across the runtimes) | |||
|  | 
 | |||
|  | 			@method shimExec | |||
|  | 			@param {Mixed} [] | |||
|  | 			@protected | |||
|  | 			@return {Mixed} Depends on the action and component | |||
|  | 			*/ | |||
|  | 			shimExec: function(component, action) { | |||
|  | 				var args = [].slice.call(arguments, 2); | |||
|  | 				return self.getShim().exec.call(this, this.uid, component, action, args); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Operaional interface that is used by components to invoke specific actions on the runtime | |||
|  | 			(is invoked in the scope of component) | |||
|  | 
 | |||
|  | 			@method exec | |||
|  | 			@param {Mixed} []* | |||
|  | 			@protected | |||
|  | 			@return {Mixed} Depends on the action and component | |||
|  | 			*/ | |||
|  | 			exec: function(component, action) { // this is called in the context of component, not runtime
 | |||
|  | 				var args = [].slice.call(arguments, 2); | |||
|  | 
 | |||
|  | 				if (self[component] && self[component][action]) { | |||
|  | 					return self[component][action].apply(this, args); | |||
|  | 				} | |||
|  | 				return self.shimExec.apply(this, arguments); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Destroys the runtime (removes all events and deletes DOM structures) | |||
|  | 
 | |||
|  | 			@method destroy | |||
|  | 			*/ | |||
|  | 			destroy: function() { | |||
|  | 				if (!self) { | |||
|  | 					return; // obviously already destroyed
 | |||
|  | 				} | |||
|  | 
 | |||
|  | 				var shimContainer = Dom.get(this.shimid); | |||
|  | 				if (shimContainer) { | |||
|  | 					shimContainer.parentNode.removeChild(shimContainer); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				if (_shim) { | |||
|  | 					_shim.removeAllInstances(); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				this.unbindAll(); | |||
|  | 				delete runtimes[this.uid]; | |||
|  | 				this.uid = null; // mark this runtime as destroyed
 | |||
|  | 				_uid = self = _shim = shimContainer = null; | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		// once we got the mode, test against all caps
 | |||
|  | 		if (this.mode && options.required_caps && !this.can(options.required_caps)) { | |||
|  | 			this.mode = false; | |||
|  | 		}	 | |||
|  | 	} | |||
|  | 
 | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Default order to try different runtime types | |||
|  | 
 | |||
|  | 	@property order | |||
|  | 	@type String | |||
|  | 	@static | |||
|  | 	*/ | |||
|  | 	Runtime.order = 'html5,html4'; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Retrieves runtime from private hash by it's uid | |||
|  | 
 | |||
|  | 	@method getRuntime | |||
|  | 	@private | |||
|  | 	@static | |||
|  | 	@param {String} uid Unique identifier of the runtime | |||
|  | 	@return {Runtime|Boolean} Returns runtime, if it exists and false, if - not | |||
|  | 	*/ | |||
|  | 	Runtime.getRuntime = function(uid) { | |||
|  | 		return runtimes[uid] ? runtimes[uid] : false; | |||
|  | 	}; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Register constructor for the Runtime of new (or perhaps modified) type | |||
|  | 
 | |||
|  | 	@method addConstructor | |||
|  | 	@static | |||
|  | 	@param {String} type Runtime type (e.g. flash, html5, etc) | |||
|  | 	@param {Function} construct Constructor for the Runtime type | |||
|  | 	*/ | |||
|  | 	Runtime.addConstructor = function(type, constructor) { | |||
|  | 		constructor.prototype = EventTarget.instance; | |||
|  | 		runtimeConstructors[type] = constructor; | |||
|  | 	}; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Get the constructor for the specified type. | |||
|  | 
 | |||
|  | 	method getConstructor | |||
|  | 	@static | |||
|  | 	@param {String} type Runtime type (e.g. flash, html5, etc) | |||
|  | 	@return {Function} Constructor for the Runtime type | |||
|  | 	*/ | |||
|  | 	Runtime.getConstructor = function(type) { | |||
|  | 		return runtimeConstructors[type] || null; | |||
|  | 	}; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Get info about the runtime (uid, type, capabilities) | |||
|  | 
 | |||
|  | 	@method getInfo | |||
|  | 	@static | |||
|  | 	@param {String} uid Unique identifier of the runtime | |||
|  | 	@return {Mixed} Info object or null if runtime doesn't exist | |||
|  | 	*/ | |||
|  | 	Runtime.getInfo = function(uid) { | |||
|  | 		var runtime = Runtime.getRuntime(uid); | |||
|  | 
 | |||
|  | 		if (runtime) { | |||
|  | 			return { | |||
|  | 				uid: runtime.uid, | |||
|  | 				type: runtime.type, | |||
|  | 				mode: runtime.mode, | |||
|  | 				can: function() { | |||
|  | 					return runtime.can.apply(runtime, arguments); | |||
|  | 				} | |||
|  | 			}; | |||
|  | 		} | |||
|  | 		return null; | |||
|  | 	}; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Convert caps represented by a comma-separated string to the object representation. | |||
|  | 
 | |||
|  | 	@method parseCaps | |||
|  | 	@static | |||
|  | 	@param {String} capStr Comma-separated list of capabilities | |||
|  | 	@return {Object} | |||
|  | 	*/ | |||
|  | 	Runtime.parseCaps = function(capStr) { | |||
|  | 		var capObj = {}; | |||
|  | 
 | |||
|  | 		if (Basic.typeOf(capStr) !== 'string') { | |||
|  | 			return capStr || {}; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		Basic.each(capStr.split(','), function(key) { | |||
|  | 			capObj[key] = true; // we assume it to be - true
 | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		return capObj; | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Test the specified runtime for specific capabilities. | |||
|  | 
 | |||
|  | 	@method can | |||
|  | 	@static | |||
|  | 	@param {String} type Runtime type (e.g. flash, html5, etc) | |||
|  | 	@param {String|Object} caps Set of capabilities to check | |||
|  | 	@return {Boolean} Result of the test | |||
|  | 	*/ | |||
|  | 	Runtime.can = function(type, caps) { | |||
|  | 		var runtime | |||
|  | 		, constructor = Runtime.getConstructor(type) | |||
|  | 		, mode | |||
|  | 		; | |||
|  | 		if (constructor) { | |||
|  | 			runtime = new constructor({ | |||
|  | 				required_caps: caps | |||
|  | 			}); | |||
|  | 			mode = runtime.mode; | |||
|  | 			runtime.destroy(); | |||
|  | 			return !!mode; | |||
|  | 		} | |||
|  | 		return false; | |||
|  | 	}; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Figure out a runtime that supports specified capabilities. | |||
|  | 
 | |||
|  | 	@method thatCan | |||
|  | 	@static | |||
|  | 	@param {String|Object} caps Set of capabilities to check | |||
|  | 	@param {String} [runtimeOrder] Comma-separated list of runtimes to check against | |||
|  | 	@return {String} Usable runtime identifier or null | |||
|  | 	*/ | |||
|  | 	Runtime.thatCan = function(caps, runtimeOrder) { | |||
|  | 		var types = (runtimeOrder || Runtime.order).split(/\s*,\s*/); | |||
|  | 		for (var i in types) { | |||
|  | 			if (Runtime.can(types[i], caps)) { | |||
|  | 				return types[i]; | |||
|  | 			} | |||
|  | 		} | |||
|  | 		return null; | |||
|  | 	}; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Figure out an operational mode for the specified set of capabilities. | |||
|  | 
 | |||
|  | 	@method getMode | |||
|  | 	@static | |||
|  | 	@param {Object} modeCaps Set of capabilities that depend on particular runtime mode | |||
|  | 	@param {Object} [requiredCaps] Supplied set of capabilities to find operational mode for | |||
|  | 	@param {String|Boolean} [defaultMode='browser'] Default mode to use  | |||
|  | 	@return {String|Boolean} Compatible operational mode | |||
|  | 	*/ | |||
|  | 	Runtime.getMode = function(modeCaps, requiredCaps, defaultMode) { | |||
|  | 		var mode = null; | |||
|  | 
 | |||
|  | 		if (Basic.typeOf(defaultMode) === 'undefined') { // only if not specified
 | |||
|  | 			defaultMode = 'browser'; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		if (requiredCaps && !Basic.isEmptyObj(modeCaps)) { | |||
|  | 			// loop over required caps and check if they do require the same mode
 | |||
|  | 			Basic.each(requiredCaps, function(value, cap) { | |||
|  | 				if (modeCaps.hasOwnProperty(cap)) { | |||
|  | 					var capMode = modeCaps[cap](value); | |||
|  | 
 | |||
|  | 					// make sure we always have an array
 | |||
|  | 					if (typeof(capMode) === 'string') { | |||
|  | 						capMode = [capMode]; | |||
|  | 					} | |||
|  | 					 | |||
|  | 					if (!mode) { | |||
|  | 						mode = capMode;						 | |||
|  | 					} else if (!(mode = Basic.arrayIntersect(mode, capMode))) { | |||
|  | 						// if cap requires conflicting mode - runtime cannot fulfill required caps
 | |||
|  | 
 | |||
|  | 						if (MXI_DEBUG && Env.debug.runtime) { | |||
|  | 							Env.log("\t\t%c: %v (conflicting mode requested: %s)", cap, value, capMode);	 | |||
|  | 						} | |||
|  | 
 | |||
|  | 						return (mode = false); | |||
|  | 					}					 | |||
|  | 				} | |||
|  | 
 | |||
|  | 				if (MXI_DEBUG && Env.debug.runtime) { | |||
|  | 					Env.log("\t\t%c: %v (compatible modes: %s)", cap, value, mode);	 | |||
|  | 				} | |||
|  | 			}); | |||
|  | 
 | |||
|  | 			if (mode) { | |||
|  | 				return Basic.inArray(defaultMode, mode) !== -1 ? defaultMode : mode[0]; | |||
|  | 			} else if (mode === false) { | |||
|  | 				return false; | |||
|  | 			} | |||
|  | 		} | |||
|  | 		return defaultMode;  | |||
|  | 	}; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Capability check that always returns true | |||
|  | 
 | |||
|  | 	@private | |||
|  | 	@static | |||
|  | 	@return {True} | |||
|  | 	*/ | |||
|  | 	Runtime.capTrue = function() { | |||
|  | 		return true; | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Capability check that always returns false | |||
|  | 
 | |||
|  | 	@private | |||
|  | 	@static | |||
|  | 	@return {False} | |||
|  | 	*/ | |||
|  | 	Runtime.capFalse = function() { | |||
|  | 		return false; | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Evaluate the expression to boolean value and create a function that always returns it. | |||
|  | 
 | |||
|  | 	@private | |||
|  | 	@static | |||
|  | 	@param {Mixed} expr Expression to evaluate | |||
|  | 	@return {Function} Function returning the result of evaluation | |||
|  | 	*/ | |||
|  | 	Runtime.capTest = function(expr) { | |||
|  | 		return function() { | |||
|  | 			return !!expr; | |||
|  | 		}; | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	return Runtime; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/RuntimeClient.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * RuntimeClient.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define('moxie/runtime/RuntimeClient', [ | |||
|  | 	'moxie/core/utils/Env', | |||
|  | 	'moxie/core/Exceptions', | |||
|  | 	'moxie/core/utils/Basic', | |||
|  | 	'moxie/runtime/Runtime' | |||
|  | ], function(Env, x, Basic, Runtime) { | |||
|  | 	/** | |||
|  | 	Set of methods and properties, required by a component to acquire ability to connect to a runtime | |||
|  | 
 | |||
|  | 	@class RuntimeClient | |||
|  | 	*/ | |||
|  | 	return function RuntimeClient() { | |||
|  | 		var runtime; | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			/** | |||
|  | 			Connects to the runtime specified by the options. Will either connect to existing runtime or create a new one. | |||
|  | 			Increments number of clients connected to the specified runtime. | |||
|  | 
 | |||
|  | 			@private | |||
|  | 			@method connectRuntime | |||
|  | 			@param {Mixed} options Can be a runtme uid or a set of key-value pairs defining requirements and pre-requisites | |||
|  | 			*/ | |||
|  | 			connectRuntime: function(options) { | |||
|  | 				var comp = this, ruid; | |||
|  | 
 | |||
|  | 				function initialize(items) { | |||
|  | 					var type, constructor; | |||
|  | 
 | |||
|  | 					// if we ran out of runtimes
 | |||
|  | 					if (!items.length) { | |||
|  | 						comp.trigger('RuntimeError', new x.RuntimeError(x.RuntimeError.NOT_INIT_ERR)); | |||
|  | 						runtime = null; | |||
|  | 						return; | |||
|  | 					} | |||
|  | 
 | |||
|  | 					type = items.shift().toLowerCase(); | |||
|  | 					constructor = Runtime.getConstructor(type); | |||
|  | 					if (!constructor) { | |||
|  | 						initialize(items); | |||
|  | 						return; | |||
|  | 					} | |||
|  | 
 | |||
|  | 					if (MXI_DEBUG && Env.debug.runtime) { | |||
|  | 						Env.log("Trying runtime: %s", type); | |||
|  | 						Env.log(options); | |||
|  | 					} | |||
|  | 
 | |||
|  | 					// try initializing the runtime
 | |||
|  | 					runtime = new constructor(options); | |||
|  | 
 | |||
|  | 					runtime.bind('Init', function() { | |||
|  | 						// mark runtime as initialized
 | |||
|  | 						runtime.initialized = true; | |||
|  | 
 | |||
|  | 						if (MXI_DEBUG && Env.debug.runtime) { | |||
|  | 							Env.log("Runtime '%s' initialized", runtime.type); | |||
|  | 						} | |||
|  | 
 | |||
|  | 						// jailbreak ...
 | |||
|  | 						setTimeout(function() { | |||
|  | 							runtime.clients++; | |||
|  | 							// this will be triggered on component
 | |||
|  | 							comp.trigger('RuntimeInit', runtime); | |||
|  | 						}, 1); | |||
|  | 					}); | |||
|  | 
 | |||
|  | 					runtime.bind('Error', function() { | |||
|  | 						if (MXI_DEBUG && Env.debug.runtime) { | |||
|  | 							Env.log("Runtime '%s' failed to initialize", runtime.type); | |||
|  | 						} | |||
|  | 
 | |||
|  | 						runtime.destroy(); // runtime cannot destroy itself from inside at a right moment, thus we do it here
 | |||
|  | 						initialize(items); | |||
|  | 					}); | |||
|  | 
 | |||
|  | 					/*runtime.bind('Exception', function() { });*/ | |||
|  | 
 | |||
|  | 					if (MXI_DEBUG && Env.debug.runtime) { | |||
|  | 						Env.log("\tselected mode: %s", runtime.mode);	 | |||
|  | 					} | |||
|  | 
 | |||
|  | 					// check if runtime managed to pick-up operational mode
 | |||
|  | 					if (!runtime.mode) { | |||
|  | 						runtime.trigger('Error'); | |||
|  | 						return; | |||
|  | 					} | |||
|  | 
 | |||
|  | 					runtime.init(); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// check if a particular runtime was requested
 | |||
|  | 				if (Basic.typeOf(options) === 'string') { | |||
|  | 					ruid = options; | |||
|  | 				} else if (Basic.typeOf(options.ruid) === 'string') { | |||
|  | 					ruid = options.ruid; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				if (ruid) { | |||
|  | 					runtime = Runtime.getRuntime(ruid); | |||
|  | 					if (runtime) { | |||
|  | 						runtime.clients++; | |||
|  | 						return runtime; | |||
|  | 					} else { | |||
|  | 						// there should be a runtime and there's none - weird case
 | |||
|  | 						throw new x.RuntimeError(x.RuntimeError.NOT_INIT_ERR); | |||
|  | 					} | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// initialize a fresh one, that fits runtime list and required features best
 | |||
|  | 				initialize((options.runtime_order || Runtime.order).split(/\s*,\s*/)); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Disconnects from the runtime. Decrements number of clients connected to the specified runtime. | |||
|  | 
 | |||
|  | 			@private | |||
|  | 			@method disconnectRuntime | |||
|  | 			*/ | |||
|  | 			disconnectRuntime: function() { | |||
|  | 				if (runtime && --runtime.clients <= 0) { | |||
|  | 					runtime.destroy(); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// once the component is disconnected, it shouldn't have access to the runtime
 | |||
|  | 				runtime = null; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Returns the runtime to which the client is currently connected. | |||
|  | 
 | |||
|  | 			@method getRuntime | |||
|  | 			@return {Runtime} Runtime or null if client is not connected | |||
|  | 			*/ | |||
|  | 			getRuntime: function() { | |||
|  | 				if (runtime && runtime.uid) { | |||
|  | 					return runtime; | |||
|  | 				} | |||
|  | 				return runtime = null; // make sure we do not leave zombies rambling around
 | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Handy shortcut to safely invoke runtime extension methods. | |||
|  | 			 | |||
|  | 			@private | |||
|  | 			@method exec | |||
|  | 			@return {Mixed} Whatever runtime extension method returns | |||
|  | 			*/ | |||
|  | 			exec: function() { | |||
|  | 				if (runtime) { | |||
|  | 					return runtime.exec.apply(this, arguments); | |||
|  | 				} | |||
|  | 				return null; | |||
|  | 			} | |||
|  | 
 | |||
|  | 		}); | |||
|  | 	}; | |||
|  | 
 | |||
|  | 
 | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/file/FileInput.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * FileInput.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define('moxie/file/FileInput', [ | |||
|  | 	'moxie/core/utils/Basic', | |||
|  | 	'moxie/core/utils/Env', | |||
|  | 	'moxie/core/utils/Mime', | |||
|  | 	'moxie/core/utils/Dom', | |||
|  | 	'moxie/core/Exceptions', | |||
|  | 	'moxie/core/EventTarget', | |||
|  | 	'moxie/core/I18n', | |||
|  | 	'moxie/runtime/Runtime', | |||
|  | 	'moxie/runtime/RuntimeClient' | |||
|  | ], function(Basic, Env, Mime, Dom, x, EventTarget, I18n, Runtime, RuntimeClient) { | |||
|  | 	/** | |||
|  | 	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 FileInput | |||
|  | 	@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. | |||
|  | 
 | |||
|  | 	@example | |||
|  | 		<div id="container"> | |||
|  | 			<a id="file-picker" href="javascript:;">Browse...</a> | |||
|  | 		</div> | |||
|  | 
 | |||
|  | 		<script> | |||
|  | 			var fileInput = new mOxie.FileInput({ | |||
|  | 				browse_button: 'file-picker', // or document.getElementById('file-picker')
 | |||
|  | 				container: 'container', | |||
|  | 				accept: [ | |||
|  | 					{title: "Image files", extensions: "jpg,gif,png"} // accept only images
 | |||
|  | 				], | |||
|  | 				multiple: true // allow multiple file selection
 | |||
|  | 			}); | |||
|  | 
 | |||
|  | 			fileInput.onchange = function(e) { | |||
|  | 				// do something to files array
 | |||
|  | 				console.info(e.target.files); // or this.files or fileInput.files
 | |||
|  | 			}; | |||
|  | 
 | |||
|  | 			fileInput.init(); // initialize
 | |||
|  | 		</script> | |||
|  | 	*/ | |||
|  | 	var dispatches = [ | |||
|  | 		/** | |||
|  | 		Dispatched when runtime is connected and file-picker is ready to be used. | |||
|  | 
 | |||
|  | 		@event ready | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'ready', | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		Dispatched right after [ready](#event_ready) event, and whenever [refresh()](#method_refresh) is invoked.  | |||
|  | 		Check [corresponding documentation entry](#method_refresh) for more info. | |||
|  | 
 | |||
|  | 		@event refresh | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		Dispatched when selection of files in the dialog is complete. | |||
|  | 
 | |||
|  | 		@event change | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'change', | |||
|  | 
 | |||
|  | 		'cancel', // TODO: might be useful
 | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		Dispatched when mouse cursor enters file-picker area. Can be used to style element | |||
|  | 		accordingly. | |||
|  | 
 | |||
|  | 		@event mouseenter | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'mouseenter', | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		Dispatched when mouse cursor leaves file-picker area. Can be used to style element | |||
|  | 		accordingly. | |||
|  | 
 | |||
|  | 		@event mouseleave | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'mouseleave', | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		Dispatched when functional mouse button is pressed on top of file-picker area. | |||
|  | 
 | |||
|  | 		@event mousedown | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'mousedown', | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		Dispatched when functional mouse button is released on top of file-picker area. | |||
|  | 
 | |||
|  | 		@event mouseup | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'mouseup' | |||
|  | 	]; | |||
|  | 
 | |||
|  | 	function FileInput(options) { | |||
|  | 		if (MXI_DEBUG) { | |||
|  | 			Env.log("Instantiating FileInput...");	 | |||
|  | 		} | |||
|  | 
 | |||
|  | 		var self = this, | |||
|  | 			container, browseButton, defaults; | |||
|  | 
 | |||
|  | 		// if flat argument passed it should be browse_button id
 | |||
|  | 		if (Basic.inArray(Basic.typeOf(options), ['string', 'node']) !== -1) { | |||
|  | 			options = { browse_button : options }; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// this will help us to find proper default container
 | |||
|  | 		browseButton = Dom.get(options.browse_button); | |||
|  | 		if (!browseButton) { | |||
|  | 			// browse button is required
 | |||
|  | 			throw new x.DOMException(x.DOMException.NOT_FOUND_ERR); | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// figure out the options
 | |||
|  | 		defaults = { | |||
|  | 			accept: [{ | |||
|  | 				title: I18n.translate('All Files'), | |||
|  | 				extensions: '*' | |||
|  | 			}], | |||
|  | 			name: 'file', | |||
|  | 			multiple: false, | |||
|  | 			required_caps: false, | |||
|  | 			container: browseButton.parentNode || document.body | |||
|  | 		}; | |||
|  | 		 | |||
|  | 		options = Basic.extend({}, defaults, options); | |||
|  | 
 | |||
|  | 		// convert to object representation
 | |||
|  | 		if (typeof(options.required_caps) === 'string') { | |||
|  | 			options.required_caps = Runtime.parseCaps(options.required_caps); | |||
|  | 		} | |||
|  | 					 | |||
|  | 		// normalize accept option (could be list of mime types or array of title/extensions pairs)
 | |||
|  | 		if (typeof(options.accept) === 'string') { | |||
|  | 			options.accept = Mime.mimes2extList(options.accept); | |||
|  | 		} | |||
|  | 
 | |||
|  | 		container = Dom.get(options.container); | |||
|  | 		// make sure we have container
 | |||
|  | 		if (!container) { | |||
|  | 			container = document.body; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// make container relative, if it's not
 | |||
|  | 		if (Dom.getStyle(container, 'position') === 'static') { | |||
|  | 			container.style.position = 'relative'; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		container = browseButton = null; // IE
 | |||
|  | 						 | |||
|  | 		RuntimeClient.call(self); | |||
|  | 		 | |||
|  | 		Basic.extend(self, { | |||
|  | 			/** | |||
|  | 			Unique id of the component | |||
|  | 
 | |||
|  | 			@property uid | |||
|  | 			@protected | |||
|  | 			@readOnly | |||
|  | 			@type {String} | |||
|  | 			@default UID | |||
|  | 			*/ | |||
|  | 			uid: Basic.guid('uid_'), | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Unique id of the connected runtime, if any. | |||
|  | 
 | |||
|  | 			@property ruid | |||
|  | 			@protected | |||
|  | 			@type {String} | |||
|  | 			*/ | |||
|  | 			ruid: null, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Unique id of the runtime container. Useful to get hold of it for various manipulations. | |||
|  | 
 | |||
|  | 			@property shimid | |||
|  | 			@protected | |||
|  | 			@type {String} | |||
|  | 			*/ | |||
|  | 			shimid: null, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Array of selected mOxie.File objects | |||
|  | 
 | |||
|  | 			@property files | |||
|  | 			@type {Array} | |||
|  | 			@default null | |||
|  | 			*/ | |||
|  | 			files: null, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Initializes the file-picker, connects it to runtime and dispatches event ready when done. | |||
|  | 
 | |||
|  | 			@method init | |||
|  | 			*/ | |||
|  | 			init: function() { | |||
|  | 				self.bind('RuntimeInit', function(e, runtime) { | |||
|  | 					self.ruid = runtime.uid; | |||
|  | 					self.shimid = runtime.shimid; | |||
|  | 
 | |||
|  | 					self.bind("Ready", function() { | |||
|  | 						self.trigger("Refresh"); | |||
|  | 					}, 999); | |||
|  | 
 | |||
|  | 					// re-position and resize shim container
 | |||
|  | 					self.bind('Refresh', function() { | |||
|  | 						var pos, size, browseButton, shimContainer; | |||
|  | 						 | |||
|  | 						browseButton = Dom.get(options.browse_button); | |||
|  | 						shimContainer = Dom.get(runtime.shimid); // do not use runtime.getShimContainer(), since it will create container if it doesn't exist
 | |||
|  | 
 | |||
|  | 						if (browseButton) { | |||
|  | 							pos = Dom.getPos(browseButton, Dom.get(options.container)); | |||
|  | 							size = Dom.getSize(browseButton); | |||
|  | 
 | |||
|  | 							if (shimContainer) { | |||
|  | 								Basic.extend(shimContainer.style, { | |||
|  | 									top     : pos.y + 'px', | |||
|  | 									left    : pos.x + 'px', | |||
|  | 									width   : size.w + 'px', | |||
|  | 									height  : size.h + 'px' | |||
|  | 								}); | |||
|  | 							} | |||
|  | 						} | |||
|  | 						shimContainer = browseButton = null; | |||
|  | 					}); | |||
|  | 					 | |||
|  | 					runtime.exec.call(self, 'FileInput', 'init', options); | |||
|  | 				}); | |||
|  | 
 | |||
|  | 				// runtime needs: options.required_features, options.runtime_order and options.container
 | |||
|  | 				self.connectRuntime(Basic.extend({}, options, { | |||
|  | 					required_caps: { | |||
|  | 						select_file: true | |||
|  | 					} | |||
|  | 				})); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Disables file-picker element, so that it doesn't react to mouse clicks. | |||
|  | 
 | |||
|  | 			@method disable | |||
|  | 			@param {Boolean} [state=true] Disable component if - true, enable if - false | |||
|  | 			*/ | |||
|  | 			disable: function(state) { | |||
|  | 				var runtime = this.getRuntime(); | |||
|  | 				if (runtime) { | |||
|  | 					runtime.exec.call(this, 'FileInput', 'disable', Basic.typeOf(state) === 'undefined' ? true : state); | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Reposition and resize dialog trigger to match the position and size of browse_button element. | |||
|  | 
 | |||
|  | 			@method refresh | |||
|  | 			*/ | |||
|  | 			refresh: function() { | |||
|  | 				self.trigger("Refresh"); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Destroy component. | |||
|  | 
 | |||
|  | 			@method destroy | |||
|  | 			*/ | |||
|  | 			destroy: function() { | |||
|  | 				var runtime = this.getRuntime(); | |||
|  | 				if (runtime) { | |||
|  | 					runtime.exec.call(this, 'FileInput', 'destroy'); | |||
|  | 					this.disconnectRuntime(); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				if (Basic.typeOf(this.files) === 'array') { | |||
|  | 					// no sense in leaving associated files behind
 | |||
|  | 					Basic.each(this.files, function(file) { | |||
|  | 						file.destroy(); | |||
|  | 					}); | |||
|  | 				}  | |||
|  | 				this.files = null; | |||
|  | 
 | |||
|  | 				this.unbindAll(); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		this.handleEventProps(dispatches); | |||
|  | 	} | |||
|  | 
 | |||
|  | 	FileInput.prototype = EventTarget.instance; | |||
|  | 
 | |||
|  | 	return FileInput; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/core/utils/Encode.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * Encode.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define('moxie/core/utils/Encode', [], function() { | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Encode string with UTF-8 | |||
|  | 
 | |||
|  | 	@method utf8_encode | |||
|  | 	@for Utils | |||
|  | 	@static | |||
|  | 	@param {String} str String to encode | |||
|  | 	@return {String} UTF-8 encoded string | |||
|  | 	*/ | |||
|  | 	var utf8_encode = function(str) { | |||
|  | 		return unescape(encodeURIComponent(str)); | |||
|  | 	}; | |||
|  | 	 | |||
|  | 	/** | |||
|  | 	Decode UTF-8 encoded string | |||
|  | 
 | |||
|  | 	@method utf8_decode | |||
|  | 	@static | |||
|  | 	@param {String} str String to decode | |||
|  | 	@return {String} Decoded string | |||
|  | 	*/ | |||
|  | 	var utf8_decode = function(str_data) { | |||
|  | 		return decodeURIComponent(escape(str_data)); | |||
|  | 	}; | |||
|  | 	 | |||
|  | 	/** | |||
|  | 	Decode Base64 encoded string (uses browser's default method if available), | |||
|  | 	from: https://raw.github.com/kvz/phpjs/master/functions/url/base64_decode.js
 | |||
|  | 
 | |||
|  | 	@method atob | |||
|  | 	@static | |||
|  | 	@param {String} data String to decode | |||
|  | 	@return {String} Decoded string | |||
|  | 	*/ | |||
|  | 	var atob = function(data, utf8) { | |||
|  | 		if (typeof(window.atob) === 'function') { | |||
|  | 			return utf8 ? utf8_decode(window.atob(data)) : window.atob(data); | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// http://kevin.vanzonneveld.net
 | |||
|  | 		// +   original by: Tyler Akins (http://rumkin.com)
 | |||
|  | 		// +   improved by: Thunder.m
 | |||
|  | 		// +      input by: Aman Gupta
 | |||
|  | 		// +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
 | |||
|  | 		// +   bugfixed by: Onno Marsman
 | |||
|  | 		// +   bugfixed by: Pellentesque Malesuada
 | |||
|  | 		// +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
 | |||
|  | 		// +      input by: Brett Zamir (http://brett-zamir.me)
 | |||
|  | 		// +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
 | |||
|  | 		// *     example 1: base64_decode('S2V2aW4gdmFuIFpvbm5ldmVsZA==');
 | |||
|  | 		// *     returns 1: 'Kevin van Zonneveld'
 | |||
|  | 		// mozilla has this native
 | |||
|  | 		// - but breaks in 2.0.0.12!
 | |||
|  | 		//if (typeof this.window.atob == 'function') {
 | |||
|  | 		//    return atob(data);
 | |||
|  | 		//}
 | |||
|  | 		var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; | |||
|  | 		var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, | |||
|  | 			ac = 0, | |||
|  | 			dec = "", | |||
|  | 			tmp_arr = []; | |||
|  | 
 | |||
|  | 		if (!data) { | |||
|  | 			return data; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		data += ''; | |||
|  | 
 | |||
|  | 		do { // unpack four hexets into three octets using index points in b64
 | |||
|  | 			h1 = b64.indexOf(data.charAt(i++)); | |||
|  | 			h2 = b64.indexOf(data.charAt(i++)); | |||
|  | 			h3 = b64.indexOf(data.charAt(i++)); | |||
|  | 			h4 = b64.indexOf(data.charAt(i++)); | |||
|  | 
 | |||
|  | 			bits = h1 << 18 | h2 << 12 | h3 << 6 | h4; | |||
|  | 
 | |||
|  | 			o1 = bits >> 16 & 0xff; | |||
|  | 			o2 = bits >> 8 & 0xff; | |||
|  | 			o3 = bits & 0xff; | |||
|  | 
 | |||
|  | 			if (h3 == 64) { | |||
|  | 				tmp_arr[ac++] = String.fromCharCode(o1); | |||
|  | 			} else if (h4 == 64) { | |||
|  | 				tmp_arr[ac++] = String.fromCharCode(o1, o2); | |||
|  | 			} else { | |||
|  | 				tmp_arr[ac++] = String.fromCharCode(o1, o2, o3); | |||
|  | 			} | |||
|  | 		} while (i < data.length); | |||
|  | 
 | |||
|  | 		dec = tmp_arr.join(''); | |||
|  | 
 | |||
|  | 		return utf8 ? utf8_decode(dec) : dec; | |||
|  | 	}; | |||
|  | 	 | |||
|  | 	/** | |||
|  | 	Base64 encode string (uses browser's default method if available), | |||
|  | 	from: https://raw.github.com/kvz/phpjs/master/functions/url/base64_encode.js
 | |||
|  | 
 | |||
|  | 	@method btoa | |||
|  | 	@static | |||
|  | 	@param {String} data String to encode | |||
|  | 	@return {String} Base64 encoded string | |||
|  | 	*/ | |||
|  | 	var btoa = function(data, utf8) { | |||
|  | 		if (utf8) { | |||
|  | 			data = utf8_encode(data); | |||
|  | 		} | |||
|  | 
 | |||
|  | 		if (typeof(window.btoa) === 'function') { | |||
|  | 			return window.btoa(data); | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// http://kevin.vanzonneveld.net
 | |||
|  | 		// +   original by: Tyler Akins (http://rumkin.com)
 | |||
|  | 		// +   improved by: Bayron Guevara
 | |||
|  | 		// +   improved by: Thunder.m
 | |||
|  | 		// +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
 | |||
|  | 		// +   bugfixed by: Pellentesque Malesuada
 | |||
|  | 		// +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
 | |||
|  | 		// +   improved by: Rafał Kukawski (http://kukawski.pl)
 | |||
|  | 		// *     example 1: base64_encode('Kevin van Zonneveld');
 | |||
|  | 		// *     returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA=='
 | |||
|  | 		// mozilla has this native
 | |||
|  | 		// - but breaks in 2.0.0.12!
 | |||
|  | 		var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; | |||
|  | 		var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, | |||
|  | 			ac = 0, | |||
|  | 			enc = "", | |||
|  | 			tmp_arr = []; | |||
|  | 
 | |||
|  | 		if (!data) { | |||
|  | 			return data; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		do { // pack three octets into four hexets
 | |||
|  | 			o1 = data.charCodeAt(i++); | |||
|  | 			o2 = data.charCodeAt(i++); | |||
|  | 			o3 = data.charCodeAt(i++); | |||
|  | 
 | |||
|  | 			bits = o1 << 16 | o2 << 8 | o3; | |||
|  | 
 | |||
|  | 			h1 = bits >> 18 & 0x3f; | |||
|  | 			h2 = bits >> 12 & 0x3f; | |||
|  | 			h3 = bits >> 6 & 0x3f; | |||
|  | 			h4 = bits & 0x3f; | |||
|  | 
 | |||
|  | 			// use hexets to index into b64, and append result to encoded string
 | |||
|  | 			tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4); | |||
|  | 		} while (i < data.length); | |||
|  | 
 | |||
|  | 		enc = tmp_arr.join(''); | |||
|  | 
 | |||
|  | 		var r = data.length % 3; | |||
|  | 
 | |||
|  | 		return (r ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3); | |||
|  | 	}; | |||
|  | 
 | |||
|  | 
 | |||
|  | 	return { | |||
|  | 		utf8_encode: utf8_encode, | |||
|  | 		utf8_decode: utf8_decode, | |||
|  | 		atob: atob, | |||
|  | 		btoa: btoa | |||
|  | 	}; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/file/Blob.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * Blob.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define('moxie/file/Blob', [ | |||
|  | 	'moxie/core/utils/Basic', | |||
|  | 	'moxie/core/utils/Encode', | |||
|  | 	'moxie/runtime/RuntimeClient' | |||
|  | ], function(Basic, Encode, RuntimeClient) { | |||
|  | 	 | |||
|  | 	var blobpool = {}; | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	@class Blob | |||
|  | 	@constructor | |||
|  | 	@param {String} ruid Unique id of the runtime, to which this blob belongs to | |||
|  | 	@param {Object} blob Object "Native" blob object, as it is represented in the runtime | |||
|  | 	*/ | |||
|  | 	function Blob(ruid, blob) { | |||
|  | 
 | |||
|  | 		function _sliceDetached(start, end, type) { | |||
|  | 			var blob, data = blobpool[this.uid]; | |||
|  | 
 | |||
|  | 			if (Basic.typeOf(data) !== 'string' || !data.length) { | |||
|  | 				return null; // or throw exception
 | |||
|  | 			} | |||
|  | 
 | |||
|  | 			blob = new Blob(null, { | |||
|  | 				type: type, | |||
|  | 				size: end - start | |||
|  | 			}); | |||
|  | 			blob.detach(data.substr(start, blob.size)); | |||
|  | 
 | |||
|  | 			return blob; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		RuntimeClient.call(this); | |||
|  | 
 | |||
|  | 		if (ruid) {	 | |||
|  | 			this.connectRuntime(ruid); | |||
|  | 		} | |||
|  | 
 | |||
|  | 		if (!blob) { | |||
|  | 			blob = {}; | |||
|  | 		} else if (Basic.typeOf(blob) === 'string') { // dataUrl or binary string
 | |||
|  | 			blob = { data: blob }; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Unique id of the component | |||
|  | 
 | |||
|  | 			@property uid | |||
|  | 			@type {String} | |||
|  | 			*/ | |||
|  | 			uid: blob.uid || Basic.guid('uid_'), | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Unique id of the connected runtime, if falsy, then runtime will have to be initialized  | |||
|  | 			before this Blob can be used, modified or sent | |||
|  | 
 | |||
|  | 			@property ruid | |||
|  | 			@type {String} | |||
|  | 			*/ | |||
|  | 			ruid: ruid, | |||
|  | 	 | |||
|  | 			/** | |||
|  | 			Size of blob | |||
|  | 
 | |||
|  | 			@property size | |||
|  | 			@type {Number} | |||
|  | 			@default 0 | |||
|  | 			*/ | |||
|  | 			size: blob.size || 0, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Mime type of blob | |||
|  | 
 | |||
|  | 			@property type | |||
|  | 			@type {String} | |||
|  | 			@default '' | |||
|  | 			*/ | |||
|  | 			type: blob.type || '', | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			@method slice | |||
|  | 			@param {Number} [start=0] | |||
|  | 			*/ | |||
|  | 			slice: function(start, end, type) {		 | |||
|  | 				if (this.isDetached()) { | |||
|  | 					return _sliceDetached.apply(this, arguments); | |||
|  | 				} | |||
|  | 				return this.getRuntime().exec.call(this, 'Blob', 'slice', this.getSource(), start, end, type); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Returns "native" blob object (as it is represented in connected runtime) or null if not found | |||
|  | 
 | |||
|  | 			@method getSource | |||
|  | 			@return {Blob} Returns "native" blob object or null if not found | |||
|  | 			*/ | |||
|  | 			getSource: function() { | |||
|  | 				if (!blobpool[this.uid]) { | |||
|  | 					return null;	 | |||
|  | 				} | |||
|  | 				return blobpool[this.uid]; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/**  | |||
|  | 			Detaches blob from any runtime that it depends on and initialize with standalone value | |||
|  | 
 | |||
|  | 			@method detach | |||
|  | 			@protected | |||
|  | 			@param {DOMString} [data=''] Standalone value | |||
|  | 			*/ | |||
|  | 			detach: function(data) { | |||
|  | 				if (this.ruid) { | |||
|  | 					this.getRuntime().exec.call(this, 'Blob', 'destroy'); | |||
|  | 					this.disconnectRuntime(); | |||
|  | 					this.ruid = null; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				data = data || ''; | |||
|  | 
 | |||
|  | 				// if dataUrl, convert to binary string
 | |||
|  | 				if (data.substr(0, 5) == 'data:') { | |||
|  | 					var base64Offset = data.indexOf(';base64,'); | |||
|  | 					this.type = data.substring(5, base64Offset); | |||
|  | 					data = Encode.atob(data.substring(base64Offset + 8)); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				this.size = data.length; | |||
|  | 
 | |||
|  | 				blobpool[this.uid] = data; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Checks if blob is standalone (detached of any runtime) | |||
|  | 			 | |||
|  | 			@method isDetached | |||
|  | 			@protected | |||
|  | 			@return {Boolean} | |||
|  | 			*/ | |||
|  | 			isDetached: function() { | |||
|  | 				return !this.ruid && Basic.typeOf(blobpool[this.uid]) === 'string'; | |||
|  | 			}, | |||
|  | 			 | |||
|  | 			/**  | |||
|  | 			Destroy Blob and free any resources it was using | |||
|  | 
 | |||
|  | 			@method destroy | |||
|  | 			*/ | |||
|  | 			destroy: function() { | |||
|  | 				this.detach(); | |||
|  | 				delete blobpool[this.uid]; | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		 | |||
|  | 		if (blob.data) { | |||
|  | 			this.detach(blob.data); // auto-detach if payload has been passed
 | |||
|  | 		} else { | |||
|  | 			blobpool[this.uid] = blob;	 | |||
|  | 		} | |||
|  | 	} | |||
|  | 	 | |||
|  | 	return Blob; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/file/File.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * File.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define('moxie/file/File', [ | |||
|  | 	'moxie/core/utils/Basic', | |||
|  | 	'moxie/core/utils/Mime', | |||
|  | 	'moxie/file/Blob' | |||
|  | ], function(Basic, Mime, Blob) { | |||
|  | 	/** | |||
|  | 	@class File | |||
|  | 	@extends Blob | |||
|  | 	@constructor | |||
|  | 	@param {String} ruid Unique id of the runtime, to which this blob belongs to | |||
|  | 	@param {Object} file Object "Native" file object, as it is represented in the runtime | |||
|  | 	*/ | |||
|  | 	function File(ruid, file) { | |||
|  | 		if (!file) { // avoid extra errors in case we overlooked something
 | |||
|  | 			file = {}; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		Blob.apply(this, arguments); | |||
|  | 
 | |||
|  | 		if (!this.type) { | |||
|  | 			this.type = Mime.getFileMime(file.name); | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// sanitize file name or generate new one
 | |||
|  | 		var name; | |||
|  | 		if (file.name) { | |||
|  | 			name = file.name.replace(/\\/g, '/'); | |||
|  | 			name = name.substr(name.lastIndexOf('/') + 1); | |||
|  | 		} else if (this.type) { | |||
|  | 			var prefix = this.type.split('/')[0]; | |||
|  | 			name = Basic.guid((prefix !== '' ? prefix : 'file') + '_'); | |||
|  | 			 | |||
|  | 			if (Mime.extensions[this.type]) { | |||
|  | 				name += '.' + Mime.extensions[this.type][0]; // append proper extension if possible
 | |||
|  | 			} | |||
|  | 		} | |||
|  | 		 | |||
|  | 		 | |||
|  | 		Basic.extend(this, { | |||
|  | 			/** | |||
|  | 			File name | |||
|  | 
 | |||
|  | 			@property name | |||
|  | 			@type {String} | |||
|  | 			@default UID | |||
|  | 			*/ | |||
|  | 			name: name || Basic.guid('file_'), | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Relative path to the file inside a directory | |||
|  | 
 | |||
|  | 			@property relativePath | |||
|  | 			@type {String} | |||
|  | 			@default '' | |||
|  | 			*/ | |||
|  | 			relativePath: '', | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Date of last modification | |||
|  | 
 | |||
|  | 			@property lastModifiedDate | |||
|  | 			@type {String} | |||
|  | 			@default now | |||
|  | 			*/ | |||
|  | 			lastModifiedDate: file.lastModifiedDate || (new Date()).toLocaleString() // Thu Aug 23 2012 19:40:00 GMT+0400 (GET)
 | |||
|  | 		}); | |||
|  | 	} | |||
|  | 
 | |||
|  | 	File.prototype = Blob.prototype; | |||
|  | 
 | |||
|  | 	return File; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/file/FileDrop.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * FileDrop.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define('moxie/file/FileDrop', [ | |||
|  | 	'moxie/core/I18n', | |||
|  | 	'moxie/core/utils/Dom', | |||
|  | 	'moxie/core/Exceptions', | |||
|  | 	'moxie/core/utils/Basic', | |||
|  | 	'moxie/core/utils/Env', | |||
|  | 	'moxie/file/File', | |||
|  | 	'moxie/runtime/RuntimeClient', | |||
|  | 	'moxie/core/EventTarget', | |||
|  | 	'moxie/core/utils/Mime' | |||
|  | ], function(I18n, Dom, x, Basic, Env, File, RuntimeClient, EventTarget, Mime) { | |||
|  | 	/** | |||
|  | 	Turn arbitrary DOM element to a drop zone accepting files. 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_. | |||
|  | 
 | |||
|  | 	@example | |||
|  | 		<div id="drop_zone"> | |||
|  | 			Drop files here | |||
|  | 		</div> | |||
|  | 		<br /> | |||
|  | 		<div id="filelist"></div> | |||
|  | 
 | |||
|  | 		<script type="text/javascript"> | |||
|  | 			var fileDrop = new mOxie.FileDrop('drop_zone'), fileList = mOxie.get('filelist'); | |||
|  | 
 | |||
|  | 			fileDrop.ondrop = function() { | |||
|  | 				mOxie.each(this.files, function(file) { | |||
|  | 					fileList.innerHTML += '<div>' + file.name + '</div>'; | |||
|  | 				}); | |||
|  | 			}; | |||
|  | 
 | |||
|  | 			fileDrop.init(); | |||
|  | 		</script> | |||
|  | 
 | |||
|  | 	@class FileDrop | |||
|  | 	@constructor | |||
|  | 	@extends EventTarget | |||
|  | 	@uses RuntimeClient | |||
|  | 	@param {Object|String} options If options has typeof string, argument is considered as options.drop_zone | |||
|  | 		@param {String|DOMElement} options.drop_zone DOM Element to turn into a drop zone | |||
|  | 		@param {Array} [options.accept] Array of mime types to accept. By default accepts all | |||
|  | 		@param {Object|String} [options.required_caps] Set of required capabilities, that chosen runtime must support | |||
|  | 	*/ | |||
|  | 	var dispatches = [ | |||
|  | 		/** | |||
|  | 		Dispatched when runtime is connected and drop zone is ready to accept files. | |||
|  | 
 | |||
|  | 		@event ready | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'ready',  | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		Dispatched when dragging cursor enters the drop zone. | |||
|  | 
 | |||
|  | 		@event dragenter | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'dragenter', | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		Dispatched when dragging cursor leaves the drop zone. | |||
|  | 
 | |||
|  | 		@event dragleave | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'dragleave',  | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		Dispatched when file is dropped onto the drop zone. | |||
|  | 
 | |||
|  | 		@event drop | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'drop',  | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		Dispatched if error occurs. | |||
|  | 
 | |||
|  | 		@event error | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'error' | |||
|  | 	]; | |||
|  | 
 | |||
|  | 	function FileDrop(options) { | |||
|  | 		if (MXI_DEBUG) { | |||
|  | 			Env.log("Instantiating FileDrop...");	 | |||
|  | 		} | |||
|  | 
 | |||
|  | 		var self = this, defaults; | |||
|  | 
 | |||
|  | 		// if flat argument passed it should be drop_zone id
 | |||
|  | 		if (typeof(options) === 'string') { | |||
|  | 			options = { drop_zone : options }; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// figure out the options
 | |||
|  | 		defaults = { | |||
|  | 			accept: [{ | |||
|  | 				title: I18n.translate('All Files'), | |||
|  | 				extensions: '*' | |||
|  | 			}], | |||
|  | 			required_caps: { | |||
|  | 				drag_and_drop: true | |||
|  | 			} | |||
|  | 		}; | |||
|  | 		 | |||
|  | 		options = typeof(options) === 'object' ? Basic.extend({}, defaults, options) : defaults; | |||
|  | 
 | |||
|  | 		// this will help us to find proper default container
 | |||
|  | 		options.container = Dom.get(options.drop_zone) || document.body; | |||
|  | 
 | |||
|  | 		// make container relative, if it is not
 | |||
|  | 		if (Dom.getStyle(options.container, 'position') === 'static') { | |||
|  | 			options.container.style.position = 'relative'; | |||
|  | 		} | |||
|  | 					 | |||
|  | 		// normalize accept option (could be list of mime types or array of title/extensions pairs)
 | |||
|  | 		if (typeof(options.accept) === 'string') { | |||
|  | 			options.accept = Mime.mimes2extList(options.accept); | |||
|  | 		} | |||
|  | 
 | |||
|  | 		RuntimeClient.call(self); | |||
|  | 
 | |||
|  | 		Basic.extend(self, { | |||
|  | 			uid: Basic.guid('uid_'), | |||
|  | 
 | |||
|  | 			ruid: null, | |||
|  | 
 | |||
|  | 			files: null, | |||
|  | 
 | |||
|  | 			init: function() {		 | |||
|  | 				self.bind('RuntimeInit', function(e, runtime) { | |||
|  | 					self.ruid = runtime.uid; | |||
|  | 					runtime.exec.call(self, 'FileDrop', 'init', options); | |||
|  | 					self.dispatchEvent('ready'); | |||
|  | 				}); | |||
|  | 							 | |||
|  | 				// runtime needs: options.required_features, options.runtime_order and options.container
 | |||
|  | 				self.connectRuntime(options); // throws RuntimeError
 | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			destroy: function() { | |||
|  | 				var runtime = this.getRuntime(); | |||
|  | 				if (runtime) { | |||
|  | 					runtime.exec.call(this, 'FileDrop', 'destroy'); | |||
|  | 					this.disconnectRuntime(); | |||
|  | 				} | |||
|  | 				this.files = null; | |||
|  | 				 | |||
|  | 				this.unbindAll(); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		this.handleEventProps(dispatches); | |||
|  | 	} | |||
|  | 
 | |||
|  | 	FileDrop.prototype = EventTarget.instance; | |||
|  | 
 | |||
|  | 	return FileDrop; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/file/FileReader.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * FileReader.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define('moxie/file/FileReader', [ | |||
|  | 	'moxie/core/utils/Basic', | |||
|  | 	'moxie/core/utils/Encode', | |||
|  | 	'moxie/core/Exceptions', | |||
|  | 	'moxie/core/EventTarget', | |||
|  | 	'moxie/file/Blob', | |||
|  | 	'moxie/runtime/RuntimeClient' | |||
|  | ], function(Basic, Encode, x, EventTarget, Blob, RuntimeClient) { | |||
|  | 	/** | |||
|  | 	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 FileReader | |||
|  | 	@constructor FileReader | |||
|  | 	@extends EventTarget | |||
|  | 	@uses RuntimeClient | |||
|  | 	*/ | |||
|  | 	var dispatches = [ | |||
|  | 
 | |||
|  | 		/**  | |||
|  | 		Dispatched when the read starts. | |||
|  | 
 | |||
|  | 		@event loadstart | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'loadstart',  | |||
|  | 
 | |||
|  | 		/**  | |||
|  | 		Dispatched while reading (and decoding) blob, and reporting partial Blob data (progess.loaded/progress.total). | |||
|  | 
 | |||
|  | 		@event progress | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'progress',  | |||
|  | 
 | |||
|  | 		/**  | |||
|  | 		Dispatched when the read has successfully completed. | |||
|  | 
 | |||
|  | 		@event load | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'load',  | |||
|  | 
 | |||
|  | 		/**  | |||
|  | 		Dispatched when the read has been aborted. For instance, by invoking the abort() method. | |||
|  | 
 | |||
|  | 		@event abort | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'abort',  | |||
|  | 
 | |||
|  | 		/**  | |||
|  | 		Dispatched when the read has failed. | |||
|  | 
 | |||
|  | 		@event error | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'error',  | |||
|  | 
 | |||
|  | 		/**  | |||
|  | 		Dispatched when the request has completed (either in success or failure). | |||
|  | 
 | |||
|  | 		@event loadend | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'loadend' | |||
|  | 	]; | |||
|  | 	 | |||
|  | 	function FileReader() { | |||
|  | 
 | |||
|  | 		RuntimeClient.call(this); | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			/** | |||
|  | 			UID of the component instance. | |||
|  | 
 | |||
|  | 			@property uid | |||
|  | 			@type {String} | |||
|  | 			*/ | |||
|  | 			uid: Basic.guid('uid_'), | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Contains current state of FileReader object. Can take values of FileReader.EMPTY, FileReader.LOADING | |||
|  | 			and FileReader.DONE. | |||
|  | 
 | |||
|  | 			@property readyState | |||
|  | 			@type {Number} | |||
|  | 			@default FileReader.EMPTY | |||
|  | 			*/ | |||
|  | 			readyState: FileReader.EMPTY, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Result of the successful read operation. | |||
|  | 
 | |||
|  | 			@property result | |||
|  | 			@type {String} | |||
|  | 			*/ | |||
|  | 			result: null, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Stores the error of failed asynchronous read operation. | |||
|  | 
 | |||
|  | 			@property error | |||
|  | 			@type {DOMError} | |||
|  | 			*/ | |||
|  | 			error: null, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Initiates reading of File/Blob object contents to binary string. | |||
|  | 
 | |||
|  | 			@method readAsBinaryString | |||
|  | 			@param {Blob|File} blob Object to preload | |||
|  | 			*/ | |||
|  | 			readAsBinaryString: function(blob) { | |||
|  | 				_read.call(this, 'readAsBinaryString', blob); | |||
|  | 			}, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Initiates reading of File/Blob object contents to dataURL string. | |||
|  | 
 | |||
|  | 			@method readAsDataURL | |||
|  | 			@param {Blob|File} blob Object to preload | |||
|  | 			*/ | |||
|  | 			readAsDataURL: function(blob) { | |||
|  | 				_read.call(this, 'readAsDataURL', blob); | |||
|  | 			}, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Initiates reading of File/Blob object contents to string. | |||
|  | 
 | |||
|  | 			@method readAsText | |||
|  | 			@param {Blob|File} blob Object to preload | |||
|  | 			*/ | |||
|  | 			readAsText: function(blob) { | |||
|  | 				_read.call(this, 'readAsText', blob); | |||
|  | 			}, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Aborts preloading process. | |||
|  | 
 | |||
|  | 			@method abort | |||
|  | 			*/ | |||
|  | 			abort: function() { | |||
|  | 				this.result = null; | |||
|  | 				 | |||
|  | 				if (Basic.inArray(this.readyState, [FileReader.EMPTY, FileReader.DONE]) !== -1) { | |||
|  | 					return; | |||
|  | 				} else if (this.readyState === FileReader.LOADING) { | |||
|  | 					this.readyState = FileReader.DONE; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				this.exec('FileReader', 'abort'); | |||
|  | 				 | |||
|  | 				this.trigger('abort'); | |||
|  | 				this.trigger('loadend'); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Destroy component and release resources. | |||
|  | 
 | |||
|  | 			@method destroy | |||
|  | 			*/ | |||
|  | 			destroy: function() { | |||
|  | 				this.abort(); | |||
|  | 				this.exec('FileReader', 'destroy'); | |||
|  | 				this.disconnectRuntime(); | |||
|  | 				this.unbindAll(); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		// uid must already be assigned
 | |||
|  | 		this.handleEventProps(dispatches); | |||
|  | 
 | |||
|  | 		this.bind('Error', function(e, err) { | |||
|  | 			this.readyState = FileReader.DONE; | |||
|  | 			this.error = err; | |||
|  | 		}, 999); | |||
|  | 		 | |||
|  | 		this.bind('Load', function(e) { | |||
|  | 			this.readyState = FileReader.DONE; | |||
|  | 		}, 999); | |||
|  | 
 | |||
|  | 		 | |||
|  | 		function _read(op, blob) { | |||
|  | 			var self = this;			 | |||
|  | 
 | |||
|  | 			this.trigger('loadstart'); | |||
|  | 
 | |||
|  | 			if (this.readyState === FileReader.LOADING) { | |||
|  | 				this.trigger('error', new x.DOMException(x.DOMException.INVALID_STATE_ERR)); | |||
|  | 				this.trigger('loadend'); | |||
|  | 				return; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			// if source is not o.Blob/o.File
 | |||
|  | 			if (!(blob instanceof Blob)) { | |||
|  | 				this.trigger('error', new x.DOMException(x.DOMException.NOT_FOUND_ERR)); | |||
|  | 				this.trigger('loadend'); | |||
|  | 				return; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			this.result = null; | |||
|  | 			this.readyState = FileReader.LOADING; | |||
|  | 			 | |||
|  | 			if (blob.isDetached()) { | |||
|  | 				var src = blob.getSource(); | |||
|  | 				switch (op) { | |||
|  | 					case 'readAsText': | |||
|  | 					case 'readAsBinaryString': | |||
|  | 						this.result = src; | |||
|  | 						break; | |||
|  | 					case 'readAsDataURL': | |||
|  | 						this.result = 'data:' + blob.type + ';base64,' + Encode.btoa(src); | |||
|  | 						break; | |||
|  | 				} | |||
|  | 				this.readyState = FileReader.DONE; | |||
|  | 				this.trigger('load'); | |||
|  | 				this.trigger('loadend'); | |||
|  | 			} else { | |||
|  | 				this.connectRuntime(blob.ruid); | |||
|  | 				this.exec('FileReader', 'read', op, blob); | |||
|  | 			} | |||
|  | 		} | |||
|  | 	} | |||
|  | 	 | |||
|  | 	/** | |||
|  | 	Initial FileReader state | |||
|  | 
 | |||
|  | 	@property EMPTY | |||
|  | 	@type {Number} | |||
|  | 	@final | |||
|  | 	@static | |||
|  | 	@default 0 | |||
|  | 	*/ | |||
|  | 	FileReader.EMPTY = 0; | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	FileReader switches to this state when it is preloading the source | |||
|  | 
 | |||
|  | 	@property LOADING | |||
|  | 	@type {Number} | |||
|  | 	@final | |||
|  | 	@static | |||
|  | 	@default 1 | |||
|  | 	*/ | |||
|  | 	FileReader.LOADING = 1; | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Preloading is complete, this is a final state | |||
|  | 
 | |||
|  | 	@property DONE | |||
|  | 	@type {Number} | |||
|  | 	@final | |||
|  | 	@static | |||
|  | 	@default 2 | |||
|  | 	*/ | |||
|  | 	FileReader.DONE = 2; | |||
|  | 
 | |||
|  | 	FileReader.prototype = EventTarget.instance; | |||
|  | 
 | |||
|  | 	return FileReader; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/core/utils/Url.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * Url.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define('moxie/core/utils/Url', [], function() { | |||
|  | 	/** | |||
|  | 	Parse url into separate components and fill in absent parts with parts from current url, | |||
|  | 	based on https://raw.github.com/kvz/phpjs/master/functions/url/parse_url.js
 | |||
|  | 
 | |||
|  | 	@method parseUrl | |||
|  | 	@for Utils | |||
|  | 	@static | |||
|  | 	@param {String} url Url to parse (defaults to empty string if undefined) | |||
|  | 	@return {Object} Hash containing extracted uri components | |||
|  | 	*/ | |||
|  | 	var parseUrl = function(url, currentUrl) { | |||
|  | 		var key = ['source', 'scheme', 'authority', 'userInfo', 'user', 'pass', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'fragment'] | |||
|  | 		, i = key.length | |||
|  | 		, ports = { | |||
|  | 			http: 80, | |||
|  | 			https: 443 | |||
|  | 		} | |||
|  | 		, uri = {} | |||
|  | 		, regex = /^(?:([^:\/?#]+):)?(?:\/\/()(?:(?:()(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?))?()(?:(()(?:(?:[^?#\/]*\/)*)()(?:[^?#]*))(?:\\?([^#]*))?(?:#(.*))?)/ | |||
|  | 		, m = regex.exec(url || '') | |||
|  | 		; | |||
|  | 					 | |||
|  | 		while (i--) { | |||
|  | 			if (m[i]) { | |||
|  | 				uri[key[i]] = m[i]; | |||
|  | 			} | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// when url is relative, we set the origin and the path ourselves
 | |||
|  | 		if (!uri.scheme) { | |||
|  | 			// come up with defaults
 | |||
|  | 			if (!currentUrl || typeof(currentUrl) === 'string') { | |||
|  | 				currentUrl = parseUrl(currentUrl || document.location.href); | |||
|  | 			} | |||
|  | 
 | |||
|  | 			uri.scheme = currentUrl.scheme; | |||
|  | 			uri.host = currentUrl.host; | |||
|  | 			uri.port = currentUrl.port; | |||
|  | 
 | |||
|  | 			var path = ''; | |||
|  | 			// for urls without trailing slash we need to figure out the path
 | |||
|  | 			if (/^[^\/]/.test(uri.path)) { | |||
|  | 				path = currentUrl.path; | |||
|  | 				// if path ends with a filename, strip it
 | |||
|  | 				if (/\/[^\/]*\.[^\/]*$/.test(path)) { | |||
|  | 					path = path.replace(/\/[^\/]+$/, '/'); | |||
|  | 				} else { | |||
|  | 					// avoid double slash at the end (see #127)
 | |||
|  | 					path = path.replace(/\/?$/, '/'); | |||
|  | 				} | |||
|  | 			} | |||
|  | 			uri.path = path + (uri.path || ''); // site may reside at domain.com or domain.com/subdir
 | |||
|  | 		} | |||
|  | 
 | |||
|  | 		if (!uri.port) { | |||
|  | 			uri.port = ports[uri.scheme] || 80; | |||
|  | 		}  | |||
|  | 		 | |||
|  | 		uri.port = parseInt(uri.port, 10); | |||
|  | 
 | |||
|  | 		if (!uri.path) { | |||
|  | 			uri.path = "/"; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		delete uri.source; | |||
|  | 
 | |||
|  | 		return uri; | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	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 | |||
|  | 	*/ | |||
|  | 	var resolveUrl = function(url) { | |||
|  | 		var ports = { // we ignore default ports
 | |||
|  | 			http: 80, | |||
|  | 			https: 443 | |||
|  | 		} | |||
|  | 		, urlp = typeof(url) === 'object' ? url : parseUrl(url); | |||
|  | 		; | |||
|  | 
 | |||
|  | 		return urlp.scheme + '://' + urlp.host + (urlp.port !== ports[urlp.scheme] ? ':' + urlp.port : '') + urlp.path + (urlp.query ? urlp.query : ''); | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Check if specified url has the same origin as the current document | |||
|  | 
 | |||
|  | 	@method hasSameOrigin | |||
|  | 	@param {String|Object} url | |||
|  | 	@return {Boolean} | |||
|  | 	*/ | |||
|  | 	var hasSameOrigin = function(url) { | |||
|  | 		function origin(url) { | |||
|  | 			return [url.scheme, url.host, url.port].join('/'); | |||
|  | 		} | |||
|  | 			 | |||
|  | 		if (typeof url === 'string') { | |||
|  | 			url = parseUrl(url); | |||
|  | 		}	 | |||
|  | 		 | |||
|  | 		return origin(parseUrl()) === origin(url); | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	return { | |||
|  | 		parseUrl: parseUrl, | |||
|  | 		resolveUrl: resolveUrl, | |||
|  | 		hasSameOrigin: hasSameOrigin | |||
|  | 	}; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/RuntimeTarget.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * RuntimeTarget.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define('moxie/runtime/RuntimeTarget', [ | |||
|  | 	'moxie/core/utils/Basic', | |||
|  | 	'moxie/runtime/RuntimeClient', | |||
|  | 	"moxie/core/EventTarget" | |||
|  | ], function(Basic, RuntimeClient, EventTarget) { | |||
|  | 	/** | |||
|  | 	Instance of this class can be used as a target for the events dispatched by shims, | |||
|  | 	when allowing them onto components is for either reason inappropriate | |||
|  | 
 | |||
|  | 	@class RuntimeTarget | |||
|  | 	@constructor | |||
|  | 	@protected | |||
|  | 	@extends EventTarget | |||
|  | 	*/ | |||
|  | 	function RuntimeTarget() { | |||
|  | 		this.uid = Basic.guid('uid_'); | |||
|  | 		 | |||
|  | 		RuntimeClient.call(this); | |||
|  | 
 | |||
|  | 		this.destroy = function() { | |||
|  | 			this.disconnectRuntime(); | |||
|  | 			this.unbindAll(); | |||
|  | 		}; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	RuntimeTarget.prototype = EventTarget.instance; | |||
|  | 
 | |||
|  | 	return RuntimeTarget; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/file/FileReaderSync.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * FileReaderSync.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define('moxie/file/FileReaderSync', [ | |||
|  | 	'moxie/core/utils/Basic', | |||
|  | 	'moxie/runtime/RuntimeClient', | |||
|  | 	'moxie/core/utils/Encode' | |||
|  | ], function(Basic, RuntimeClient, Encode) { | |||
|  | 	/** | |||
|  | 	Synchronous FileReader implementation. Something like this is available in WebWorkers environment, here | |||
|  | 	it can be used to read only preloaded blobs/files and only below certain size (not yet sure what that'd be, | |||
|  | 	but probably < 1mb). Not meant to be used directly by user. | |||
|  | 
 | |||
|  | 	@class FileReaderSync | |||
|  | 	@private | |||
|  | 	@constructor | |||
|  | 	*/ | |||
|  | 	return function() { | |||
|  | 		RuntimeClient.call(this); | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			uid: Basic.guid('uid_'), | |||
|  | 
 | |||
|  | 			readAsBinaryString: function(blob) { | |||
|  | 				return _read.call(this, 'readAsBinaryString', blob); | |||
|  | 			}, | |||
|  | 			 | |||
|  | 			readAsDataURL: function(blob) { | |||
|  | 				return _read.call(this, 'readAsDataURL', blob); | |||
|  | 			}, | |||
|  | 			 | |||
|  | 			/*readAsArrayBuffer: function(blob) { | |||
|  | 				return _read.call(this, 'readAsArrayBuffer', blob); | |||
|  | 			},*/ | |||
|  | 			 | |||
|  | 			readAsText: function(blob) { | |||
|  | 				return _read.call(this, 'readAsText', blob); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		function _read(op, blob) { | |||
|  | 			if (blob.isDetached()) { | |||
|  | 				var src = blob.getSource(); | |||
|  | 				switch (op) { | |||
|  | 					case 'readAsBinaryString': | |||
|  | 						return src; | |||
|  | 					case 'readAsDataURL': | |||
|  | 						return 'data:' + blob.type + ';base64,' + Encode.btoa(src); | |||
|  | 					case 'readAsText': | |||
|  | 						var txt = ''; | |||
|  | 						for (var i = 0, length = src.length; i < length; i++) { | |||
|  | 							txt += String.fromCharCode(src[i]); | |||
|  | 						} | |||
|  | 						return txt; | |||
|  | 				} | |||
|  | 			} else { | |||
|  | 				var result = this.connectRuntime(blob.ruid).exec.call(this, 'FileReaderSync', 'read', op, blob); | |||
|  | 				this.disconnectRuntime(); | |||
|  | 				return result; | |||
|  | 			} | |||
|  | 		} | |||
|  | 	}; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/xhr/FormData.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * FormData.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define("moxie/xhr/FormData", [ | |||
|  | 	"moxie/core/Exceptions", | |||
|  | 	"moxie/core/utils/Basic", | |||
|  | 	"moxie/file/Blob" | |||
|  | ], function(x, Basic, Blob) { | |||
|  | 	/** | |||
|  | 	FormData | |||
|  | 
 | |||
|  | 	@class FormData | |||
|  | 	@constructor | |||
|  | 	*/ | |||
|  | 	function FormData() { | |||
|  | 		var _blob, _fields = []; | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			/** | |||
|  | 			Append another key-value pair to the FormData object | |||
|  | 
 | |||
|  | 			@method append | |||
|  | 			@param {String} name Name for the new field | |||
|  | 			@param {String|Blob|Array|Object} value Value for the field | |||
|  | 			*/ | |||
|  | 			append: function(name, value) { | |||
|  | 				var self = this, valueType = Basic.typeOf(value); | |||
|  | 
 | |||
|  | 				// according to specs value might be either Blob or String
 | |||
|  | 				if (value instanceof Blob) { | |||
|  | 					_blob = { | |||
|  | 						name: name, | |||
|  | 						value: value // unfortunately we can only send single Blob in one FormData
 | |||
|  | 					}; | |||
|  | 				} else if ('array' === valueType) { | |||
|  | 					name += '[]'; | |||
|  | 
 | |||
|  | 					Basic.each(value, function(value) { | |||
|  | 						self.append(name, value); | |||
|  | 					}); | |||
|  | 				} else if ('object' === valueType) { | |||
|  | 					Basic.each(value, function(value, key) { | |||
|  | 						self.append(name + '[' + key + ']', value); | |||
|  | 					}); | |||
|  | 				} else if ('null' === valueType || 'undefined' === valueType || 'number' === valueType && isNaN(value)) { | |||
|  | 					self.append(name, "false"); | |||
|  | 				} else { | |||
|  | 					_fields.push({ | |||
|  | 						name: name, | |||
|  | 						value: value.toString() | |||
|  | 					}); | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Checks if FormData contains Blob. | |||
|  | 
 | |||
|  | 			@method hasBlob | |||
|  | 			@return {Boolean} | |||
|  | 			*/ | |||
|  | 			hasBlob: function() { | |||
|  | 				return !!this.getBlob(); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Retrieves blob. | |||
|  | 
 | |||
|  | 			@method getBlob | |||
|  | 			@return {Object} Either Blob if found or null | |||
|  | 			*/ | |||
|  | 			getBlob: function() { | |||
|  | 				return _blob && _blob.value || null; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Retrieves blob field name. | |||
|  | 
 | |||
|  | 			@method getBlobName | |||
|  | 			@return {String} Either Blob field name or null | |||
|  | 			*/ | |||
|  | 			getBlobName: function() { | |||
|  | 				return _blob && _blob.name || null; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Loop over the fields in FormData and invoke the callback for each of them. | |||
|  | 
 | |||
|  | 			@method each | |||
|  | 			@param {Function} cb Callback to call for each field | |||
|  | 			*/ | |||
|  | 			each: function(cb) { | |||
|  | 				Basic.each(_fields, function(field) { | |||
|  | 					cb(field.value, field.name); | |||
|  | 				}); | |||
|  | 
 | |||
|  | 				if (_blob) { | |||
|  | 					cb(_blob.value, _blob.name); | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			destroy: function() { | |||
|  | 				_blob = null; | |||
|  | 				_fields = []; | |||
|  | 			} | |||
|  | 		}); | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return FormData; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/xhr/XMLHttpRequest.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * XMLHttpRequest.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define("moxie/xhr/XMLHttpRequest", [ | |||
|  | 	"moxie/core/utils/Basic", | |||
|  | 	"moxie/core/Exceptions", | |||
|  | 	"moxie/core/EventTarget", | |||
|  | 	"moxie/core/utils/Encode", | |||
|  | 	"moxie/core/utils/Url", | |||
|  | 	"moxie/runtime/Runtime", | |||
|  | 	"moxie/runtime/RuntimeTarget", | |||
|  | 	"moxie/file/Blob", | |||
|  | 	"moxie/file/FileReaderSync", | |||
|  | 	"moxie/xhr/FormData", | |||
|  | 	"moxie/core/utils/Env", | |||
|  | 	"moxie/core/utils/Mime" | |||
|  | ], function(Basic, x, EventTarget, Encode, Url, Runtime, RuntimeTarget, Blob, FileReaderSync, FormData, Env, Mime) { | |||
|  | 
 | |||
|  | 	var httpCode = { | |||
|  | 		100: 'Continue', | |||
|  | 		101: 'Switching Protocols', | |||
|  | 		102: 'Processing', | |||
|  | 
 | |||
|  | 		200: 'OK', | |||
|  | 		201: 'Created', | |||
|  | 		202: 'Accepted', | |||
|  | 		203: 'Non-Authoritative Information', | |||
|  | 		204: 'No Content', | |||
|  | 		205: 'Reset Content', | |||
|  | 		206: 'Partial Content', | |||
|  | 		207: 'Multi-Status', | |||
|  | 		226: 'IM Used', | |||
|  | 
 | |||
|  | 		300: 'Multiple Choices', | |||
|  | 		301: 'Moved Permanently', | |||
|  | 		302: 'Found', | |||
|  | 		303: 'See Other', | |||
|  | 		304: 'Not Modified', | |||
|  | 		305: 'Use Proxy', | |||
|  | 		306: 'Reserved', | |||
|  | 		307: 'Temporary Redirect', | |||
|  | 
 | |||
|  | 		400: 'Bad Request', | |||
|  | 		401: 'Unauthorized', | |||
|  | 		402: 'Payment Required', | |||
|  | 		403: 'Forbidden', | |||
|  | 		404: 'Not Found', | |||
|  | 		405: 'Method Not Allowed', | |||
|  | 		406: 'Not Acceptable', | |||
|  | 		407: 'Proxy Authentication Required', | |||
|  | 		408: 'Request Timeout', | |||
|  | 		409: 'Conflict', | |||
|  | 		410: 'Gone', | |||
|  | 		411: 'Length Required', | |||
|  | 		412: 'Precondition Failed', | |||
|  | 		413: 'Request Entity Too Large', | |||
|  | 		414: 'Request-URI Too Long', | |||
|  | 		415: 'Unsupported Media Type', | |||
|  | 		416: 'Requested Range Not Satisfiable', | |||
|  | 		417: 'Expectation Failed', | |||
|  | 		422: 'Unprocessable Entity', | |||
|  | 		423: 'Locked', | |||
|  | 		424: 'Failed Dependency', | |||
|  | 		426: 'Upgrade Required', | |||
|  | 
 | |||
|  | 		500: 'Internal Server Error', | |||
|  | 		501: 'Not Implemented', | |||
|  | 		502: 'Bad Gateway', | |||
|  | 		503: 'Service Unavailable', | |||
|  | 		504: 'Gateway Timeout', | |||
|  | 		505: 'HTTP Version Not Supported', | |||
|  | 		506: 'Variant Also Negotiates', | |||
|  | 		507: 'Insufficient Storage', | |||
|  | 		510: 'Not Extended' | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	function XMLHttpRequestUpload() { | |||
|  | 		this.uid = Basic.guid('uid_'); | |||
|  | 	} | |||
|  | 	 | |||
|  | 	XMLHttpRequestUpload.prototype = EventTarget.instance; | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Implementation of XMLHttpRequest | |||
|  | 
 | |||
|  | 	@class XMLHttpRequest | |||
|  | 	@constructor | |||
|  | 	@uses RuntimeClient | |||
|  | 	@extends EventTarget | |||
|  | 	*/ | |||
|  | 	var dispatches = [ | |||
|  | 		'loadstart', | |||
|  | 
 | |||
|  | 		'progress', | |||
|  | 
 | |||
|  | 		'abort', | |||
|  | 
 | |||
|  | 		'error', | |||
|  | 
 | |||
|  | 		'load', | |||
|  | 
 | |||
|  | 		'timeout', | |||
|  | 
 | |||
|  | 		'loadend' | |||
|  | 
 | |||
|  | 		// readystatechange (for historical reasons)
 | |||
|  | 	];  | |||
|  | 	 | |||
|  | 	var NATIVE = 1, RUNTIME = 2; | |||
|  | 					 | |||
|  | 	function XMLHttpRequest() { | |||
|  | 		var self = this, | |||
|  | 			// this (together with _p() @see below) is here to gracefully upgrade to setter/getter syntax where possible
 | |||
|  | 			props = { | |||
|  | 				/** | |||
|  | 				The amount of milliseconds a request can take before being terminated. Initially zero. Zero means there is no timeout. | |||
|  | 
 | |||
|  | 				@property timeout | |||
|  | 				@type Number | |||
|  | 				@default 0 | |||
|  | 				*/ | |||
|  | 				timeout: 0, | |||
|  | 
 | |||
|  | 				/** | |||
|  | 				Current state, can take following values: | |||
|  | 				UNSENT (numeric value 0) | |||
|  | 				The object has been constructed. | |||
|  | 
 | |||
|  | 				OPENED (numeric value 1) | |||
|  | 				The open() method has been successfully invoked. During this state request headers can be set using setRequestHeader() and the request can be made using the send() method. | |||
|  | 
 | |||
|  | 				HEADERS_RECEIVED (numeric value 2) | |||
|  | 				All redirects (if any) have been followed and all HTTP headers of the final response have been received. Several response members of the object are now available. | |||
|  | 
 | |||
|  | 				LOADING (numeric value 3) | |||
|  | 				The response entity body is being received. | |||
|  | 
 | |||
|  | 				DONE (numeric value 4) | |||
|  | 
 | |||
|  | 				@property readyState | |||
|  | 				@type Number | |||
|  | 				@default 0 (UNSENT) | |||
|  | 				*/ | |||
|  | 				readyState: XMLHttpRequest.UNSENT, | |||
|  | 
 | |||
|  | 				/** | |||
|  | 				True when user credentials are to be included in a cross-origin request. False when they are to be excluded | |||
|  | 				in a cross-origin request and when cookies are to be ignored in its response. Initially false. | |||
|  | 
 | |||
|  | 				@property withCredentials | |||
|  | 				@type Boolean | |||
|  | 				@default false | |||
|  | 				*/ | |||
|  | 				withCredentials: false, | |||
|  | 
 | |||
|  | 				/** | |||
|  | 				Returns the HTTP status code. | |||
|  | 
 | |||
|  | 				@property status | |||
|  | 				@type Number | |||
|  | 				@default 0 | |||
|  | 				*/ | |||
|  | 				status: 0, | |||
|  | 
 | |||
|  | 				/** | |||
|  | 				Returns the HTTP status text. | |||
|  | 
 | |||
|  | 				@property statusText | |||
|  | 				@type String | |||
|  | 				*/ | |||
|  | 				statusText: "", | |||
|  | 
 | |||
|  | 				/** | |||
|  | 				Returns the response type. Can be set to change the response type. Values are: | |||
|  | 				the empty string (default), "arraybuffer", "blob", "document", "json", and "text". | |||
|  | 				 | |||
|  | 				@property responseType | |||
|  | 				@type String | |||
|  | 				*/ | |||
|  | 				responseType: "", | |||
|  | 
 | |||
|  | 				/** | |||
|  | 				Returns the document response entity body. | |||
|  | 				 | |||
|  | 				Throws an "InvalidStateError" exception if responseType is not the empty string or "document". | |||
|  | 
 | |||
|  | 				@property responseXML | |||
|  | 				@type Document | |||
|  | 				*/ | |||
|  | 				responseXML: null, | |||
|  | 
 | |||
|  | 				/** | |||
|  | 				Returns the text response entity body. | |||
|  | 				 | |||
|  | 				Throws an "InvalidStateError" exception if responseType is not the empty string or "text". | |||
|  | 
 | |||
|  | 				@property responseText | |||
|  | 				@type String | |||
|  | 				*/ | |||
|  | 				responseText: null, | |||
|  | 
 | |||
|  | 				/** | |||
|  | 				Returns the response entity body (http://www.w3.org/TR/XMLHttpRequest/#response-entity-body).
 | |||
|  | 				Can become: ArrayBuffer, Blob, Document, JSON, Text | |||
|  | 				 | |||
|  | 				@property response | |||
|  | 				@type Mixed | |||
|  | 				*/ | |||
|  | 				response: null | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			_async = true, | |||
|  | 			_url, | |||
|  | 			_method, | |||
|  | 			_headers = {}, | |||
|  | 			_user, | |||
|  | 			_password, | |||
|  | 			_encoding = null, | |||
|  | 			_mimeType = null, | |||
|  | 
 | |||
|  | 			// flags
 | |||
|  | 			_sync_flag = false, | |||
|  | 			_send_flag = false, | |||
|  | 			_upload_events_flag = false, | |||
|  | 			_upload_complete_flag = false, | |||
|  | 			_error_flag = false, | |||
|  | 			_same_origin_flag = false, | |||
|  | 
 | |||
|  | 			// times
 | |||
|  | 			_start_time, | |||
|  | 			_timeoutset_time, | |||
|  | 
 | |||
|  | 			_finalMime = null, | |||
|  | 			_finalCharset = null, | |||
|  | 
 | |||
|  | 			_options = {}, | |||
|  | 			_xhr, | |||
|  | 			_responseHeaders = '', | |||
|  | 			_responseHeadersBag | |||
|  | 			; | |||
|  | 
 | |||
|  | 		 | |||
|  | 		Basic.extend(this, props, { | |||
|  | 			/** | |||
|  | 			Unique id of the component | |||
|  | 
 | |||
|  | 			@property uid | |||
|  | 			@type String | |||
|  | 			*/ | |||
|  | 			uid: Basic.guid('uid_'), | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Target for Upload events | |||
|  | 
 | |||
|  | 			@property upload | |||
|  | 			@type XMLHttpRequestUpload | |||
|  | 			*/ | |||
|  | 			upload: new XMLHttpRequestUpload(), | |||
|  | 			 | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Sets the request method, request URL, synchronous flag, request username, and request password. | |||
|  | 
 | |||
|  | 			Throws a "SyntaxError" exception if one of the following is true: | |||
|  | 
 | |||
|  | 			method is not a valid HTTP method. | |||
|  | 			url cannot be resolved. | |||
|  | 			url contains the "user:password" format in the userinfo production. | |||
|  | 			Throws a "SecurityError" exception if method is a case-insensitive match for CONNECT, TRACE or TRACK. | |||
|  | 
 | |||
|  | 			Throws an "InvalidAccessError" exception if one of the following is true: | |||
|  | 
 | |||
|  | 			Either user or password is passed as argument and the origin of url does not match the XMLHttpRequest origin. | |||
|  | 			There is an associated XMLHttpRequest document and either the timeout attribute is not zero, | |||
|  | 			the withCredentials attribute is true, or the responseType attribute is not the empty string. | |||
|  | 
 | |||
|  | 
 | |||
|  | 			@method open | |||
|  | 			@param {String} method HTTP method to use on request | |||
|  | 			@param {String} url URL to request | |||
|  | 			@param {Boolean} [async=true] If false request will be done in synchronous manner. Asynchronous by default. | |||
|  | 			@param {String} [user] Username to use in HTTP authentication process on server-side | |||
|  | 			@param {String} [password] Password to use in HTTP authentication process on server-side | |||
|  | 			*/ | |||
|  | 			open: function(method, url, async, user, password) { | |||
|  | 				var urlp; | |||
|  | 				 | |||
|  | 				// first two arguments are required
 | |||
|  | 				if (!method || !url) { | |||
|  | 					throw new x.DOMException(x.DOMException.SYNTAX_ERR); | |||
|  | 				} | |||
|  | 				 | |||
|  | 				// 2 - check if any code point in method is higher than U+00FF or after deflating method it does not match the method
 | |||
|  | 				if (/[\u0100-\uffff]/.test(method) || Encode.utf8_encode(method) !== method) { | |||
|  | 					throw new x.DOMException(x.DOMException.SYNTAX_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// 3
 | |||
|  | 				if (!!~Basic.inArray(method.toUpperCase(), ['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT', 'TRACE', 'TRACK'])) { | |||
|  | 					_method = method.toUpperCase(); | |||
|  | 				} | |||
|  | 				 | |||
|  | 				 | |||
|  | 				// 4 - allowing these methods poses a security risk
 | |||
|  | 				if (!!~Basic.inArray(_method, ['CONNECT', 'TRACE', 'TRACK'])) { | |||
|  | 					throw new x.DOMException(x.DOMException.SECURITY_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// 5
 | |||
|  | 				url = Encode.utf8_encode(url); | |||
|  | 				 | |||
|  | 				// 6 - Resolve url relative to the XMLHttpRequest base URL. If the algorithm returns an error, throw a "SyntaxError".
 | |||
|  | 				urlp = Url.parseUrl(url); | |||
|  | 
 | |||
|  | 				_same_origin_flag = Url.hasSameOrigin(urlp); | |||
|  | 																 | |||
|  | 				// 7 - manually build up absolute url
 | |||
|  | 				_url = Url.resolveUrl(url); | |||
|  | 		 | |||
|  | 				// 9-10, 12-13
 | |||
|  | 				if ((user || password) && !_same_origin_flag) { | |||
|  | 					throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				_user = user || urlp.user; | |||
|  | 				_password = password || urlp.pass; | |||
|  | 				 | |||
|  | 				// 11
 | |||
|  | 				_async = async || true; | |||
|  | 				 | |||
|  | 				if (_async === false && (_p('timeout') || _p('withCredentials') || _p('responseType') !== "")) { | |||
|  | 					throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR); | |||
|  | 				} | |||
|  | 				 | |||
|  | 				// 14 - terminate abort()
 | |||
|  | 				 | |||
|  | 				// 15 - terminate send()
 | |||
|  | 
 | |||
|  | 				// 18
 | |||
|  | 				_sync_flag = !_async; | |||
|  | 				_send_flag = false; | |||
|  | 				_headers = {}; | |||
|  | 				_reset.call(this); | |||
|  | 
 | |||
|  | 				// 19
 | |||
|  | 				_p('readyState', XMLHttpRequest.OPENED); | |||
|  | 				 | |||
|  | 				// 20
 | |||
|  | 				this.dispatchEvent('readystatechange'); | |||
|  | 			}, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Appends an header to the list of author request headers, or if header is already | |||
|  | 			in the list of author request headers, combines its value with value. | |||
|  | 
 | |||
|  | 			Throws an "InvalidStateError" exception if the state is not OPENED or if the send() flag is set. | |||
|  | 			Throws a "SyntaxError" exception if header is not a valid HTTP header field name or if value | |||
|  | 			is not a valid HTTP header field value. | |||
|  | 			 | |||
|  | 			@method setRequestHeader | |||
|  | 			@param {String} header | |||
|  | 			@param {String|Number} value | |||
|  | 			*/ | |||
|  | 			setRequestHeader: function(header, value) { | |||
|  | 				var uaHeaders = [ // these headers are controlled by the user agent
 | |||
|  | 						"accept-charset", | |||
|  | 						"accept-encoding", | |||
|  | 						"access-control-request-headers", | |||
|  | 						"access-control-request-method", | |||
|  | 						"connection", | |||
|  | 						"content-length", | |||
|  | 						"cookie", | |||
|  | 						"cookie2", | |||
|  | 						"content-transfer-encoding", | |||
|  | 						"date", | |||
|  | 						"expect", | |||
|  | 						"host", | |||
|  | 						"keep-alive", | |||
|  | 						"origin", | |||
|  | 						"referer", | |||
|  | 						"te", | |||
|  | 						"trailer", | |||
|  | 						"transfer-encoding", | |||
|  | 						"upgrade", | |||
|  | 						"user-agent", | |||
|  | 						"via" | |||
|  | 					]; | |||
|  | 				 | |||
|  | 				// 1-2
 | |||
|  | 				if (_p('readyState') !== XMLHttpRequest.OPENED || _send_flag) { | |||
|  | 					throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// 3
 | |||
|  | 				if (/[\u0100-\uffff]/.test(header) || Encode.utf8_encode(header) !== header) { | |||
|  | 					throw new x.DOMException(x.DOMException.SYNTAX_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// 4
 | |||
|  | 				/* this step is seemingly bypassed in browsers, probably to allow various unicode characters in header values | |||
|  | 				if (/[\u0100-\uffff]/.test(value) || Encode.utf8_encode(value) !== value) { | |||
|  | 					throw new x.DOMException(x.DOMException.SYNTAX_ERR); | |||
|  | 				}*/ | |||
|  | 
 | |||
|  | 				header = Basic.trim(header).toLowerCase(); | |||
|  | 				 | |||
|  | 				// setting of proxy-* and sec-* headers is prohibited by spec
 | |||
|  | 				if (!!~Basic.inArray(header, uaHeaders) || /^(proxy\-|sec\-)/.test(header)) { | |||
|  | 					return false; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// camelize
 | |||
|  | 				// browsers lowercase header names (at least for custom ones)
 | |||
|  | 				// header = header.replace(/\b\w/g, function($1) { return $1.toUpperCase(); });
 | |||
|  | 				 | |||
|  | 				if (!_headers[header]) { | |||
|  | 					_headers[header] = value; | |||
|  | 				} else { | |||
|  | 					// http://tools.ietf.org/html/rfc2616#section-4.2 (last paragraph)
 | |||
|  | 					_headers[header] += ', ' + value; | |||
|  | 				} | |||
|  | 				return true; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Returns all headers from the response, with the exception of those whose field name is Set-Cookie or Set-Cookie2. | |||
|  | 
 | |||
|  | 			@method getAllResponseHeaders | |||
|  | 			@return {String} reponse headers or empty string | |||
|  | 			*/ | |||
|  | 			getAllResponseHeaders: function() { | |||
|  | 				return _responseHeaders || ''; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Returns the header field value from the response of which the field name matches header,  | |||
|  | 			unless the field name is Set-Cookie or Set-Cookie2. | |||
|  | 
 | |||
|  | 			@method getResponseHeader | |||
|  | 			@param {String} header | |||
|  | 			@return {String} value(s) for the specified header or null | |||
|  | 			*/ | |||
|  | 			getResponseHeader: function(header) { | |||
|  | 				header = header.toLowerCase(); | |||
|  | 
 | |||
|  | 				if (_error_flag || !!~Basic.inArray(header, ['set-cookie', 'set-cookie2'])) { | |||
|  | 					return null; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				if (_responseHeaders && _responseHeaders !== '') { | |||
|  | 					// if we didn't parse response headers until now, do it and keep for later
 | |||
|  | 					if (!_responseHeadersBag) { | |||
|  | 						_responseHeadersBag = {}; | |||
|  | 						Basic.each(_responseHeaders.split(/\r\n/), function(line) { | |||
|  | 							var pair = line.split(/:\s+/); | |||
|  | 							if (pair.length === 2) { // last line might be empty, omit
 | |||
|  | 								pair[0] = Basic.trim(pair[0]); // just in case
 | |||
|  | 								_responseHeadersBag[pair[0].toLowerCase()] = { // simply to retain header name in original form
 | |||
|  | 									header: pair[0], | |||
|  | 									value: Basic.trim(pair[1]) | |||
|  | 								}; | |||
|  | 							} | |||
|  | 						}); | |||
|  | 					} | |||
|  | 					if (_responseHeadersBag.hasOwnProperty(header)) { | |||
|  | 						return _responseHeadersBag[header].header + ': ' + _responseHeadersBag[header].value; | |||
|  | 					} | |||
|  | 				} | |||
|  | 				return null; | |||
|  | 			}, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Sets the Content-Type header for the response to mime. | |||
|  | 			Throws an "InvalidStateError" exception if the state is LOADING or DONE. | |||
|  | 			Throws a "SyntaxError" exception if mime is not a valid media type. | |||
|  | 
 | |||
|  | 			@method overrideMimeType | |||
|  | 			@param String mime Mime type to set | |||
|  | 			*/ | |||
|  | 			overrideMimeType: function(mime) { | |||
|  | 				var matches, charset; | |||
|  | 			 | |||
|  | 				// 1
 | |||
|  | 				if (!!~Basic.inArray(_p('readyState'), [XMLHttpRequest.LOADING, XMLHttpRequest.DONE])) { | |||
|  | 					throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// 2
 | |||
|  | 				mime = Basic.trim(mime.toLowerCase()); | |||
|  | 
 | |||
|  | 				if (/;/.test(mime) && (matches = mime.match(/^([^;]+)(?:;\scharset\=)?(.*)$/))) { | |||
|  | 					mime = matches[1]; | |||
|  | 					if (matches[2]) { | |||
|  | 						charset = matches[2]; | |||
|  | 					} | |||
|  | 				} | |||
|  | 
 | |||
|  | 				if (!Mime.mimes[mime]) { | |||
|  | 					throw new x.DOMException(x.DOMException.SYNTAX_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// 3-4
 | |||
|  | 				_finalMime = mime; | |||
|  | 				_finalCharset = charset; | |||
|  | 			}, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Initiates the request. The optional argument provides the request entity body. | |||
|  | 			The argument is ignored if request method is GET or HEAD. | |||
|  | 
 | |||
|  | 			Throws an "InvalidStateError" exception if the state is not OPENED or if the send() flag is set. | |||
|  | 
 | |||
|  | 			@method send | |||
|  | 			@param {Blob|Document|String|FormData} [data] Request entity body | |||
|  | 			@param {Object} [options] Set of requirements and pre-requisities for runtime initialization | |||
|  | 			*/ | |||
|  | 			send: function(data, options) {					 | |||
|  | 				if (Basic.typeOf(options) === 'string') { | |||
|  | 					_options = { ruid: options }; | |||
|  | 				} else if (!options) { | |||
|  | 					_options = {}; | |||
|  | 				} else { | |||
|  | 					_options = options; | |||
|  | 				} | |||
|  | 															 | |||
|  | 				// 1-2
 | |||
|  | 				if (this.readyState !== XMLHttpRequest.OPENED || _send_flag) { | |||
|  | 					throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); | |||
|  | 				} | |||
|  | 				 | |||
|  | 				// 3					
 | |||
|  | 				// sending Blob
 | |||
|  | 				if (data instanceof Blob) { | |||
|  | 					_options.ruid = data.ruid; | |||
|  | 					_mimeType = data.type || 'application/octet-stream'; | |||
|  | 				} | |||
|  | 				 | |||
|  | 				// FormData
 | |||
|  | 				else if (data instanceof FormData) { | |||
|  | 					if (data.hasBlob()) { | |||
|  | 						var blob = data.getBlob(); | |||
|  | 						_options.ruid = blob.ruid; | |||
|  | 						_mimeType = blob.type || 'application/octet-stream'; | |||
|  | 					} | |||
|  | 				} | |||
|  | 				 | |||
|  | 				// DOMString
 | |||
|  | 				else if (typeof data === 'string') { | |||
|  | 					_encoding = 'UTF-8'; | |||
|  | 					_mimeType = 'text/plain;charset=UTF-8'; | |||
|  | 					 | |||
|  | 					// data should be converted to Unicode and encoded as UTF-8
 | |||
|  | 					data = Encode.utf8_encode(data); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// if withCredentials not set, but requested, set it automatically
 | |||
|  | 				if (!this.withCredentials) { | |||
|  | 					this.withCredentials = (_options.required_caps && _options.required_caps.send_browser_cookies) && !_same_origin_flag; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// 4 - storage mutex
 | |||
|  | 				// 5
 | |||
|  | 				_upload_events_flag = (!_sync_flag && this.upload.hasEventListener()); // DSAP
 | |||
|  | 				// 6
 | |||
|  | 				_error_flag = false; | |||
|  | 				// 7
 | |||
|  | 				_upload_complete_flag = !data; | |||
|  | 				// 8 - Asynchronous steps
 | |||
|  | 				if (!_sync_flag) { | |||
|  | 					// 8.1
 | |||
|  | 					_send_flag = true; | |||
|  | 					// 8.2
 | |||
|  | 					// this.dispatchEvent('loadstart'); // will be dispatched either by native or runtime xhr
 | |||
|  | 					// 8.3
 | |||
|  | 					//if (!_upload_complete_flag) {
 | |||
|  | 						// this.upload.dispatchEvent('loadstart');	// will be dispatched either by native or runtime xhr
 | |||
|  | 					//}
 | |||
|  | 				} | |||
|  | 				// 8.5 - Return the send() method call, but continue running the steps in this algorithm.
 | |||
|  | 				_doXHR.call(this, data); | |||
|  | 			}, | |||
|  | 			 | |||
|  | 			/** | |||
|  | 			Cancels any network activity. | |||
|  | 			 | |||
|  | 			@method abort | |||
|  | 			*/ | |||
|  | 			abort: function() { | |||
|  | 				_error_flag = true; | |||
|  | 				_sync_flag = false; | |||
|  | 
 | |||
|  | 				if (!~Basic.inArray(_p('readyState'), [XMLHttpRequest.UNSENT, XMLHttpRequest.OPENED, XMLHttpRequest.DONE])) { | |||
|  | 					_p('readyState', XMLHttpRequest.DONE); | |||
|  | 					_send_flag = false; | |||
|  | 
 | |||
|  | 					if (_xhr) { | |||
|  | 						_xhr.getRuntime().exec.call(_xhr, 'XMLHttpRequest', 'abort', _upload_complete_flag); | |||
|  | 					} else { | |||
|  | 						throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); | |||
|  | 					} | |||
|  | 
 | |||
|  | 					_upload_complete_flag = true; | |||
|  | 				} else { | |||
|  | 					_p('readyState', XMLHttpRequest.UNSENT); | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			destroy: function() { | |||
|  | 				if (_xhr) { | |||
|  | 					if (Basic.typeOf(_xhr.destroy) === 'function') { | |||
|  | 						_xhr.destroy(); | |||
|  | 					} | |||
|  | 					_xhr = null; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				this.unbindAll(); | |||
|  | 
 | |||
|  | 				if (this.upload) { | |||
|  | 					this.upload.unbindAll(); | |||
|  | 					this.upload = null; | |||
|  | 				} | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		this.handleEventProps(dispatches.concat(['readystatechange'])); // for historical reasons
 | |||
|  | 		this.upload.handleEventProps(dispatches); | |||
|  | 
 | |||
|  | 		/* this is nice, but maybe too lengthy | |||
|  | 
 | |||
|  | 		// if supported by JS version, set getters/setters for specific properties
 | |||
|  | 		o.defineProperty(this, 'readyState', { | |||
|  | 			configurable: false, | |||
|  | 
 | |||
|  | 			get: function() { | |||
|  | 				return _p('readyState'); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		o.defineProperty(this, 'timeout', { | |||
|  | 			configurable: false, | |||
|  | 
 | |||
|  | 			get: function() { | |||
|  | 				return _p('timeout'); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			set: function(value) { | |||
|  | 
 | |||
|  | 				if (_sync_flag) { | |||
|  | 					throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// timeout still should be measured relative to the start time of request
 | |||
|  | 				_timeoutset_time = (new Date).getTime(); | |||
|  | 
 | |||
|  | 				_p('timeout', value); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		// the withCredentials attribute has no effect when fetching same-origin resources
 | |||
|  | 		o.defineProperty(this, 'withCredentials', { | |||
|  | 			configurable: false, | |||
|  | 
 | |||
|  | 			get: function() { | |||
|  | 				return _p('withCredentials'); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			set: function(value) { | |||
|  | 				// 1-2
 | |||
|  | 				if (!~o.inArray(_p('readyState'), [XMLHttpRequest.UNSENT, XMLHttpRequest.OPENED]) || _send_flag) { | |||
|  | 					throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// 3-4
 | |||
|  | 				if (_anonymous_flag || _sync_flag) { | |||
|  | 					throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// 5
 | |||
|  | 				_p('withCredentials', value); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		o.defineProperty(this, 'status', { | |||
|  | 			configurable: false, | |||
|  | 
 | |||
|  | 			get: function() { | |||
|  | 				return _p('status'); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		o.defineProperty(this, 'statusText', { | |||
|  | 			configurable: false, | |||
|  | 
 | |||
|  | 			get: function() { | |||
|  | 				return _p('statusText'); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		o.defineProperty(this, 'responseType', { | |||
|  | 			configurable: false, | |||
|  | 
 | |||
|  | 			get: function() { | |||
|  | 				return _p('responseType'); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			set: function(value) { | |||
|  | 				// 1
 | |||
|  | 				if (!!~o.inArray(_p('readyState'), [XMLHttpRequest.LOADING, XMLHttpRequest.DONE])) { | |||
|  | 					throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// 2
 | |||
|  | 				if (_sync_flag) { | |||
|  | 					throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// 3
 | |||
|  | 				_p('responseType', value.toLowerCase()); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		o.defineProperty(this, 'responseText', { | |||
|  | 			configurable: false, | |||
|  | 
 | |||
|  | 			get: function() { | |||
|  | 				// 1
 | |||
|  | 				if (!~o.inArray(_p('responseType'), ['', 'text'])) { | |||
|  | 					throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// 2-3
 | |||
|  | 				if (_p('readyState') !== XMLHttpRequest.DONE && _p('readyState') !== XMLHttpRequest.LOADING || _error_flag) { | |||
|  | 					throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				return _p('responseText'); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		o.defineProperty(this, 'responseXML', { | |||
|  | 			configurable: false, | |||
|  | 
 | |||
|  | 			get: function() { | |||
|  | 				// 1
 | |||
|  | 				if (!~o.inArray(_p('responseType'), ['', 'document'])) { | |||
|  | 					throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// 2-3
 | |||
|  | 				if (_p('readyState') !== XMLHttpRequest.DONE || _error_flag) { | |||
|  | 					throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				return _p('responseXML'); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		o.defineProperty(this, 'response', { | |||
|  | 			configurable: false, | |||
|  | 
 | |||
|  | 			get: function() { | |||
|  | 				if (!!~o.inArray(_p('responseType'), ['', 'text'])) { | |||
|  | 					if (_p('readyState') !== XMLHttpRequest.DONE && _p('readyState') !== XMLHttpRequest.LOADING || _error_flag) { | |||
|  | 						return ''; | |||
|  | 					} | |||
|  | 				} | |||
|  | 
 | |||
|  | 				if (_p('readyState') !== XMLHttpRequest.DONE || _error_flag) { | |||
|  | 					return null; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				return _p('response'); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		*/ | |||
|  | 
 | |||
|  | 		function _p(prop, value) { | |||
|  | 			if (!props.hasOwnProperty(prop)) { | |||
|  | 				return; | |||
|  | 			} | |||
|  | 			if (arguments.length === 1) { // get
 | |||
|  | 				return Env.can('define_property') ? props[prop] : self[prop]; | |||
|  | 			} else { // set
 | |||
|  | 				if (Env.can('define_property')) { | |||
|  | 					props[prop] = value; | |||
|  | 				} else { | |||
|  | 					self[prop] = value; | |||
|  | 				} | |||
|  | 			} | |||
|  | 		} | |||
|  | 		 | |||
|  | 		/* | |||
|  | 		function _toASCII(str, AllowUnassigned, UseSTD3ASCIIRules) { | |||
|  | 			// TODO: http://tools.ietf.org/html/rfc3490#section-4.1
 | |||
|  | 			return str.toLowerCase(); | |||
|  | 		} | |||
|  | 		*/ | |||
|  | 		 | |||
|  | 		 | |||
|  | 		function _doXHR(data) { | |||
|  | 			var self = this; | |||
|  | 			 | |||
|  | 			_start_time = new Date().getTime(); | |||
|  | 
 | |||
|  | 			_xhr = new RuntimeTarget(); | |||
|  | 
 | |||
|  | 			function loadEnd() { | |||
|  | 				if (_xhr) { // it could have been destroyed by now
 | |||
|  | 					_xhr.destroy(); | |||
|  | 					_xhr = null; | |||
|  | 				} | |||
|  | 				self.dispatchEvent('loadend'); | |||
|  | 				self = null; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			function exec(runtime) { | |||
|  | 				_xhr.bind('LoadStart', function(e) { | |||
|  | 					_p('readyState', XMLHttpRequest.LOADING); | |||
|  | 					self.dispatchEvent('readystatechange'); | |||
|  | 
 | |||
|  | 					self.dispatchEvent(e); | |||
|  | 					 | |||
|  | 					if (_upload_events_flag) { | |||
|  | 						self.upload.dispatchEvent(e); | |||
|  | 					} | |||
|  | 				}); | |||
|  | 				 | |||
|  | 				_xhr.bind('Progress', function(e) { | |||
|  | 					if (_p('readyState') !== XMLHttpRequest.LOADING) { | |||
|  | 						_p('readyState', XMLHttpRequest.LOADING); // LoadStart unreliable (in Flash for example)
 | |||
|  | 						self.dispatchEvent('readystatechange'); | |||
|  | 					} | |||
|  | 					self.dispatchEvent(e); | |||
|  | 				}); | |||
|  | 				 | |||
|  | 				_xhr.bind('UploadProgress', function(e) { | |||
|  | 					if (_upload_events_flag) { | |||
|  | 						self.upload.dispatchEvent({ | |||
|  | 							type: 'progress', | |||
|  | 							lengthComputable: false, | |||
|  | 							total: e.total, | |||
|  | 							loaded: e.loaded | |||
|  | 						}); | |||
|  | 					} | |||
|  | 				}); | |||
|  | 				 | |||
|  | 				_xhr.bind('Load', function(e) { | |||
|  | 					_p('readyState', XMLHttpRequest.DONE); | |||
|  | 					_p('status', Number(runtime.exec.call(_xhr, 'XMLHttpRequest', 'getStatus') || 0)); | |||
|  | 					_p('statusText', httpCode[_p('status')] || ""); | |||
|  | 					 | |||
|  | 					_p('response', runtime.exec.call(_xhr, 'XMLHttpRequest', 'getResponse', _p('responseType'))); | |||
|  | 
 | |||
|  | 					if (!!~Basic.inArray(_p('responseType'), ['text', ''])) { | |||
|  | 						_p('responseText', _p('response')); | |||
|  | 					} else if (_p('responseType') === 'document') { | |||
|  | 						_p('responseXML', _p('response')); | |||
|  | 					} | |||
|  | 
 | |||
|  | 					_responseHeaders = runtime.exec.call(_xhr, 'XMLHttpRequest', 'getAllResponseHeaders'); | |||
|  | 
 | |||
|  | 					self.dispatchEvent('readystatechange'); | |||
|  | 					 | |||
|  | 					if (_p('status') > 0) { // status 0 usually means that server is unreachable
 | |||
|  | 						if (_upload_events_flag) { | |||
|  | 							self.upload.dispatchEvent(e); | |||
|  | 						} | |||
|  | 						self.dispatchEvent(e); | |||
|  | 					} else { | |||
|  | 						_error_flag = true; | |||
|  | 						self.dispatchEvent('error'); | |||
|  | 					} | |||
|  | 					loadEnd(); | |||
|  | 				}); | |||
|  | 
 | |||
|  | 				_xhr.bind('Abort', function(e) { | |||
|  | 					self.dispatchEvent(e); | |||
|  | 					loadEnd(); | |||
|  | 				}); | |||
|  | 				 | |||
|  | 				_xhr.bind('Error', function(e) { | |||
|  | 					_error_flag = true; | |||
|  | 					_p('readyState', XMLHttpRequest.DONE); | |||
|  | 					self.dispatchEvent('readystatechange'); | |||
|  | 					_upload_complete_flag = true; | |||
|  | 					self.dispatchEvent(e); | |||
|  | 					loadEnd(); | |||
|  | 				}); | |||
|  | 
 | |||
|  | 				runtime.exec.call(_xhr, 'XMLHttpRequest', 'send', { | |||
|  | 					url: _url, | |||
|  | 					method: _method, | |||
|  | 					async: _async, | |||
|  | 					user: _user, | |||
|  | 					password: _password, | |||
|  | 					headers: _headers, | |||
|  | 					mimeType: _mimeType, | |||
|  | 					encoding: _encoding, | |||
|  | 					responseType: self.responseType, | |||
|  | 					withCredentials: self.withCredentials, | |||
|  | 					options: _options | |||
|  | 				}, data); | |||
|  | 			} | |||
|  | 
 | |||
|  | 			// clarify our requirements
 | |||
|  | 			if (typeof(_options.required_caps) === 'string') { | |||
|  | 				_options.required_caps = Runtime.parseCaps(_options.required_caps); | |||
|  | 			} | |||
|  | 
 | |||
|  | 			_options.required_caps = Basic.extend({}, _options.required_caps, { | |||
|  | 				return_response_type: self.responseType | |||
|  | 			}); | |||
|  | 
 | |||
|  | 			if (data instanceof FormData) { | |||
|  | 				_options.required_caps.send_multipart = true; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			if (!Basic.isEmptyObj(_headers)) { | |||
|  | 				_options.required_caps.send_custom_headers = true; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			if (!_same_origin_flag) { | |||
|  | 				_options.required_caps.do_cors = true; | |||
|  | 			} | |||
|  | 			 | |||
|  | 
 | |||
|  | 			if (_options.ruid) { // we do not need to wait if we can connect directly
 | |||
|  | 				exec(_xhr.connectRuntime(_options)); | |||
|  | 			} else { | |||
|  | 				_xhr.bind('RuntimeInit', function(e, runtime) { | |||
|  | 					exec(runtime); | |||
|  | 				}); | |||
|  | 				_xhr.bind('RuntimeError', function(e, err) { | |||
|  | 					self.dispatchEvent('RuntimeError', err); | |||
|  | 				}); | |||
|  | 				_xhr.connectRuntime(_options); | |||
|  | 			} | |||
|  | 		} | |||
|  | 	 | |||
|  | 		 | |||
|  | 		function _reset() { | |||
|  | 			_p('responseText', ""); | |||
|  | 			_p('responseXML', null); | |||
|  | 			_p('response', null); | |||
|  | 			_p('status', 0); | |||
|  | 			_p('statusText', ""); | |||
|  | 			_start_time = _timeoutset_time = null; | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	XMLHttpRequest.UNSENT = 0; | |||
|  | 	XMLHttpRequest.OPENED = 1; | |||
|  | 	XMLHttpRequest.HEADERS_RECEIVED = 2; | |||
|  | 	XMLHttpRequest.LOADING = 3; | |||
|  | 	XMLHttpRequest.DONE = 4; | |||
|  | 	 | |||
|  | 	XMLHttpRequest.prototype = EventTarget.instance; | |||
|  | 
 | |||
|  | 	return XMLHttpRequest; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/Transporter.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * Transporter.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define("moxie/runtime/Transporter", [ | |||
|  | 	"moxie/core/utils/Basic", | |||
|  | 	"moxie/core/utils/Encode", | |||
|  | 	"moxie/runtime/RuntimeClient", | |||
|  | 	"moxie/core/EventTarget" | |||
|  | ], function(Basic, Encode, RuntimeClient, EventTarget) { | |||
|  | 	function Transporter() { | |||
|  | 		var mod, _runtime, _data, _size, _pos, _chunk_size; | |||
|  | 
 | |||
|  | 		RuntimeClient.call(this); | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			uid: Basic.guid('uid_'), | |||
|  | 
 | |||
|  | 			state: Transporter.IDLE, | |||
|  | 
 | |||
|  | 			result: null, | |||
|  | 
 | |||
|  | 			transport: function(data, type, options) { | |||
|  | 				var self = this; | |||
|  | 
 | |||
|  | 				options = Basic.extend({ | |||
|  | 					chunk_size: 204798 | |||
|  | 				}, options); | |||
|  | 
 | |||
|  | 				// should divide by three, base64 requires this
 | |||
|  | 				if ((mod = options.chunk_size % 3)) { | |||
|  | 					options.chunk_size += 3 - mod; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				_chunk_size = options.chunk_size; | |||
|  | 
 | |||
|  | 				_reset.call(this); | |||
|  | 				_data = data; | |||
|  | 				_size = data.length; | |||
|  | 
 | |||
|  | 				if (Basic.typeOf(options) === 'string' || options.ruid) { | |||
|  | 					_run.call(self, type, this.connectRuntime(options)); | |||
|  | 				} else { | |||
|  | 					// we require this to run only once
 | |||
|  | 					var cb = function(e, runtime) { | |||
|  | 						self.unbind("RuntimeInit", cb); | |||
|  | 						_run.call(self, type, runtime); | |||
|  | 					}; | |||
|  | 					this.bind("RuntimeInit", cb); | |||
|  | 					this.connectRuntime(options); | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			abort: function() { | |||
|  | 				var self = this; | |||
|  | 
 | |||
|  | 				self.state = Transporter.IDLE; | |||
|  | 				if (_runtime) { | |||
|  | 					_runtime.exec.call(self, 'Transporter', 'clear'); | |||
|  | 					self.trigger("TransportingAborted"); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				_reset.call(self); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			destroy: function() { | |||
|  | 				this.unbindAll(); | |||
|  | 				_runtime = null; | |||
|  | 				this.disconnectRuntime(); | |||
|  | 				_reset.call(this); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		function _reset() { | |||
|  | 			_size = _pos = 0; | |||
|  | 			_data = this.result = null; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		function _run(type, runtime) { | |||
|  | 			var self = this; | |||
|  | 
 | |||
|  | 			_runtime = runtime; | |||
|  | 
 | |||
|  | 			//self.unbind("RuntimeInit");
 | |||
|  | 
 | |||
|  | 			self.bind("TransportingProgress", function(e) { | |||
|  | 				_pos = e.loaded; | |||
|  | 
 | |||
|  | 				if (_pos < _size && Basic.inArray(self.state, [Transporter.IDLE, Transporter.DONE]) === -1) { | |||
|  | 					_transport.call(self); | |||
|  | 				} | |||
|  | 			}, 999); | |||
|  | 
 | |||
|  | 			self.bind("TransportingComplete", function() { | |||
|  | 				_pos = _size; | |||
|  | 				self.state = Transporter.DONE; | |||
|  | 				_data = null; // clean a bit
 | |||
|  | 				self.result = _runtime.exec.call(self, 'Transporter', 'getAsBlob', type || ''); | |||
|  | 			}, 999); | |||
|  | 
 | |||
|  | 			self.state = Transporter.BUSY; | |||
|  | 			self.trigger("TransportingStarted"); | |||
|  | 			_transport.call(self); | |||
|  | 		} | |||
|  | 
 | |||
|  | 		function _transport() { | |||
|  | 			var self = this, | |||
|  | 				chunk, | |||
|  | 				bytesLeft = _size - _pos; | |||
|  | 
 | |||
|  | 			if (_chunk_size > bytesLeft) { | |||
|  | 				_chunk_size = bytesLeft; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			chunk = Encode.btoa(_data.substr(_pos, _chunk_size)); | |||
|  | 			_runtime.exec.call(self, 'Transporter', 'receive', chunk, _size); | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	Transporter.IDLE = 0; | |||
|  | 	Transporter.BUSY = 1; | |||
|  | 	Transporter.DONE = 2; | |||
|  | 
 | |||
|  | 	Transporter.prototype = EventTarget.instance; | |||
|  | 
 | |||
|  | 	return Transporter; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/image/Image.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * Image.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define("moxie/image/Image", [ | |||
|  | 	"moxie/core/utils/Basic", | |||
|  | 	"moxie/core/utils/Dom", | |||
|  | 	"moxie/core/Exceptions", | |||
|  | 	"moxie/file/FileReaderSync", | |||
|  | 	"moxie/xhr/XMLHttpRequest", | |||
|  | 	"moxie/runtime/Runtime", | |||
|  | 	"moxie/runtime/RuntimeClient", | |||
|  | 	"moxie/runtime/Transporter", | |||
|  | 	"moxie/core/utils/Env", | |||
|  | 	"moxie/core/EventTarget", | |||
|  | 	"moxie/file/Blob", | |||
|  | 	"moxie/file/File", | |||
|  | 	"moxie/core/utils/Encode" | |||
|  | ], function(Basic, Dom, x, FileReaderSync, XMLHttpRequest, Runtime, RuntimeClient, Transporter, Env, EventTarget, Blob, File, Encode) { | |||
|  | 	/** | |||
|  | 	Image preloading and manipulation utility. Additionally it provides access to image meta info (Exif, GPS) and raw binary data. | |||
|  | 
 | |||
|  | 	@class Image | |||
|  | 	@constructor | |||
|  | 	@extends EventTarget | |||
|  | 	*/ | |||
|  | 	var dispatches = [ | |||
|  | 		'progress', | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		Dispatched when loading is complete. | |||
|  | 
 | |||
|  | 		@event load | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'load', | |||
|  | 
 | |||
|  | 		'error', | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		Dispatched when resize operation is complete. | |||
|  | 		 | |||
|  | 		@event resize | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'resize', | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		Dispatched when visual representation of the image is successfully embedded | |||
|  | 		into the corresponsing container. | |||
|  | 
 | |||
|  | 		@event embedded | |||
|  | 		@param {Object} event | |||
|  | 		*/ | |||
|  | 		'embedded' | |||
|  | 	]; | |||
|  | 
 | |||
|  | 	function Image() { | |||
|  | 
 | |||
|  | 		RuntimeClient.call(this); | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			/** | |||
|  | 			Unique id of the component | |||
|  | 
 | |||
|  | 			@property uid | |||
|  | 			@type {String} | |||
|  | 			*/ | |||
|  | 			uid: Basic.guid('uid_'), | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Unique id of the connected runtime, if any. | |||
|  | 
 | |||
|  | 			@property ruid | |||
|  | 			@type {String} | |||
|  | 			*/ | |||
|  | 			ruid: null, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Name of the file, that was used to create an image, if available. If not equals to empty string. | |||
|  | 
 | |||
|  | 			@property name | |||
|  | 			@type {String} | |||
|  | 			@default "" | |||
|  | 			*/ | |||
|  | 			name: "", | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Size of the image in bytes. Actual value is set only after image is preloaded. | |||
|  | 
 | |||
|  | 			@property size | |||
|  | 			@type {Number} | |||
|  | 			@default 0 | |||
|  | 			*/ | |||
|  | 			size: 0, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Width of the image. Actual value is set only after image is preloaded. | |||
|  | 
 | |||
|  | 			@property width | |||
|  | 			@type {Number} | |||
|  | 			@default 0 | |||
|  | 			*/ | |||
|  | 			width: 0, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Height of the image. Actual value is set only after image is preloaded. | |||
|  | 
 | |||
|  | 			@property height | |||
|  | 			@type {Number} | |||
|  | 			@default 0 | |||
|  | 			*/ | |||
|  | 			height: 0, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Mime type of the image. Currently only image/jpeg and image/png are supported. Actual value is set only after image is preloaded. | |||
|  | 
 | |||
|  | 			@property type | |||
|  | 			@type {String} | |||
|  | 			@default "" | |||
|  | 			*/ | |||
|  | 			type: "", | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Holds meta info (Exif, GPS). Is populated only for image/jpeg. Actual value is set only after image is preloaded. | |||
|  | 
 | |||
|  | 			@property meta | |||
|  | 			@type {Object} | |||
|  | 			@default {} | |||
|  | 			*/ | |||
|  | 			meta: {}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Alias for load method, that takes another mOxie.Image object as a source (see load). | |||
|  | 
 | |||
|  | 			@method clone | |||
|  | 			@param {Image} src Source for the image | |||
|  | 			@param {Boolean} [exact=false] Whether to activate in-depth clone mode | |||
|  | 			*/ | |||
|  | 			clone: function() { | |||
|  | 				this.load.apply(this, arguments); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Loads image from various sources. Currently the source for new image can be: mOxie.Image, mOxie.Blob/mOxie.File,  | |||
|  | 			native Blob/File, dataUrl or URL. Depending on the type of the source, arguments - differ. When source is URL,  | |||
|  | 			Image will be downloaded from remote destination and loaded in memory. | |||
|  | 
 | |||
|  | 			@example | |||
|  | 				var img = new mOxie.Image(); | |||
|  | 				img.onload = function() { | |||
|  | 					var blob = img.getAsBlob(); | |||
|  | 					 | |||
|  | 					var formData = new mOxie.FormData(); | |||
|  | 					formData.append('file', blob); | |||
|  | 
 | |||
|  | 					var xhr = new mOxie.XMLHttpRequest(); | |||
|  | 					xhr.onload = function() { | |||
|  | 						// upload complete
 | |||
|  | 					}; | |||
|  | 					xhr.open('post', 'upload.php'); | |||
|  | 					xhr.send(formData); | |||
|  | 				}; | |||
|  | 				img.load("http://www.moxiecode.com/images/mox-logo.jpg"); // notice file extension (.jpg)
 | |||
|  | 			 | |||
|  | 
 | |||
|  | 			@method load | |||
|  | 			@param {Image|Blob|File|String} src Source for the image | |||
|  | 			@param {Boolean|Object} [mixed] | |||
|  | 			*/ | |||
|  | 			load: function() { | |||
|  | 				_load.apply(this, arguments); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Downsizes the image to fit the specified width/height. If crop is supplied, image will be cropped to exact dimensions. | |||
|  | 
 | |||
|  | 			@method downsize | |||
|  | 			@param {Object} opts | |||
|  | 				@param {Number} opts.width Resulting width | |||
|  | 				@param {Number} [opts.height=width] Resulting height (optional, if not supplied will default to width) | |||
|  | 				@param {Boolean} [opts.crop=false] Whether to crop the image to exact dimensions | |||
|  | 				@param {Boolean} [opts.preserveHeaders=true] Whether to preserve meta headers (on JPEGs after resize) | |||
|  | 				@param {String} [opts.resample=false] Resampling algorithm to use for resizing | |||
|  | 			*/ | |||
|  | 			downsize: function(opts) { | |||
|  | 				var defaults = { | |||
|  | 					width: this.width, | |||
|  | 					height: this.height, | |||
|  | 					type: this.type || 'image/jpeg', | |||
|  | 					quality: 90, | |||
|  | 					crop: false, | |||
|  | 					preserveHeaders: true, | |||
|  | 					resample: false | |||
|  | 				}; | |||
|  | 
 | |||
|  | 				if (typeof(opts) === 'object') { | |||
|  | 					opts = Basic.extend(defaults, opts); | |||
|  | 				} else { | |||
|  | 					// for backward compatibility
 | |||
|  | 					opts = Basic.extend(defaults, { | |||
|  | 						width: arguments[0], | |||
|  | 						height: arguments[1], | |||
|  | 						crop: arguments[2], | |||
|  | 						preserveHeaders: arguments[3] | |||
|  | 					}); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				try { | |||
|  | 					if (!this.size) { // only preloaded image objects can be used as source
 | |||
|  | 						throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); | |||
|  | 					} | |||
|  | 
 | |||
|  | 					// no way to reliably intercept the crash due to high resolution, so we simply avoid it
 | |||
|  | 					if (this.width > Image.MAX_RESIZE_WIDTH || this.height > Image.MAX_RESIZE_HEIGHT) { | |||
|  | 						throw new x.ImageError(x.ImageError.MAX_RESOLUTION_ERR); | |||
|  | 					} | |||
|  | 
 | |||
|  | 					this.exec('Image', 'downsize', opts.width, opts.height, opts.crop, opts.preserveHeaders); | |||
|  | 				} catch(ex) { | |||
|  | 					// for now simply trigger error event
 | |||
|  | 					this.trigger('error', ex.code); | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Alias for downsize(width, height, true). (see downsize) | |||
|  | 			 | |||
|  | 			@method crop | |||
|  | 			@param {Number} width Resulting width | |||
|  | 			@param {Number} [height=width] Resulting height (optional, if not supplied will default to width) | |||
|  | 			@param {Boolean} [preserveHeaders=true] Whether to preserve meta headers (on JPEGs after resize) | |||
|  | 			*/ | |||
|  | 			crop: function(width, height, preserveHeaders) { | |||
|  | 				this.downsize(width, height, true, preserveHeaders); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			getAsCanvas: function() { | |||
|  | 				if (!Env.can('create_canvas')) { | |||
|  | 					throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				var runtime = this.connectRuntime(this.ruid); | |||
|  | 				return runtime.exec.call(this, 'Image', 'getAsCanvas'); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Retrieves image in it's current state as mOxie.Blob object. Cannot be run on empty or image in progress (throws | |||
|  | 			DOMException.INVALID_STATE_ERR). | |||
|  | 
 | |||
|  | 			@method getAsBlob | |||
|  | 			@param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png | |||
|  | 			@param {Number} [quality=90] Applicable only together with mime type image/jpeg | |||
|  | 			@return {Blob} Image as Blob | |||
|  | 			*/ | |||
|  | 			getAsBlob: function(type, quality) { | |||
|  | 				if (!this.size) { | |||
|  | 					throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); | |||
|  | 				} | |||
|  | 				return this.exec('Image', 'getAsBlob', type || 'image/jpeg', quality || 90); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Retrieves image in it's current state as dataURL string. Cannot be run on empty or image in progress (throws | |||
|  | 			DOMException.INVALID_STATE_ERR). | |||
|  | 
 | |||
|  | 			@method getAsDataURL | |||
|  | 			@param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png | |||
|  | 			@param {Number} [quality=90] Applicable only together with mime type image/jpeg | |||
|  | 			@return {String} Image as dataURL string | |||
|  | 			*/ | |||
|  | 			getAsDataURL: function(type, quality) { | |||
|  | 				if (!this.size) { | |||
|  | 					throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); | |||
|  | 				} | |||
|  | 				return this.exec('Image', 'getAsDataURL', type || 'image/jpeg', quality || 90); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Retrieves image in it's current state as binary string. Cannot be run on empty or image in progress (throws | |||
|  | 			DOMException.INVALID_STATE_ERR). | |||
|  | 
 | |||
|  | 			@method getAsBinaryString | |||
|  | 			@param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png | |||
|  | 			@param {Number} [quality=90] Applicable only together with mime type image/jpeg | |||
|  | 			@return {String} Image as binary string | |||
|  | 			*/ | |||
|  | 			getAsBinaryString: function(type, quality) { | |||
|  | 				var dataUrl = this.getAsDataURL(type, quality); | |||
|  | 				return Encode.atob(dataUrl.substring(dataUrl.indexOf('base64,') + 7)); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Embeds a visual representation of the image into the specified node. Depending on the runtime,  | |||
|  | 			it might be a canvas, an img node or a thrid party shim object (Flash or SilverLight - very rare,  | |||
|  | 			can be used in legacy browsers that do not have canvas or proper dataURI support). | |||
|  | 
 | |||
|  | 			@method embed | |||
|  | 			@param {DOMElement} el DOM element to insert the image object into | |||
|  | 			@param {Object} [opts] | |||
|  | 				@param {Number} [opts.width] The width of an embed (defaults to the image width) | |||
|  | 				@param {Number} [opts.height] The height of an embed (defaults to the image height) | |||
|  | 				@param {String} [type="image/jpeg"] Mime type | |||
|  | 				@param {Number} [quality=90] Quality of an embed, if mime type is image/jpeg | |||
|  | 				@param {Boolean} [crop=false] Whether to crop an embed to the specified dimensions | |||
|  | 			*/ | |||
|  | 			embed: function(el, opts) { | |||
|  | 				var self = this | |||
|  | 				, runtime // this has to be outside of all the closures to contain proper runtime
 | |||
|  | 				; | |||
|  | 
 | |||
|  | 				opts = Basic.extend({ | |||
|  | 					width: this.width, | |||
|  | 					height: this.height, | |||
|  | 					type: this.type || 'image/jpeg', | |||
|  | 					quality: 90 | |||
|  | 				}, opts || {}); | |||
|  | 				 | |||
|  | 
 | |||
|  | 				function render(type, quality) { | |||
|  | 					var img = this; | |||
|  | 
 | |||
|  | 					// if possible, embed a canvas element directly
 | |||
|  | 					if (Env.can('create_canvas')) { | |||
|  | 						var canvas = img.getAsCanvas(); | |||
|  | 						if (canvas) { | |||
|  | 							el.appendChild(canvas); | |||
|  | 							canvas = null; | |||
|  | 							img.destroy(); | |||
|  | 							self.trigger('embedded'); | |||
|  | 							return; | |||
|  | 						} | |||
|  | 					} | |||
|  | 
 | |||
|  | 					var dataUrl = img.getAsDataURL(type, quality); | |||
|  | 					if (!dataUrl) { | |||
|  | 						throw new x.ImageError(x.ImageError.WRONG_FORMAT); | |||
|  | 					} | |||
|  | 
 | |||
|  | 					if (Env.can('use_data_uri_of', dataUrl.length)) { | |||
|  | 						el.innerHTML = '<img src="' + dataUrl + '" width="' + img.width + '" height="' + img.height + '" />'; | |||
|  | 						img.destroy(); | |||
|  | 						self.trigger('embedded'); | |||
|  | 					} else { | |||
|  | 						var tr = new Transporter(); | |||
|  | 
 | |||
|  | 						tr.bind("TransportingComplete", function() { | |||
|  | 							runtime = self.connectRuntime(this.result.ruid); | |||
|  | 
 | |||
|  | 							self.bind("Embedded", function() { | |||
|  | 								// position and size properly
 | |||
|  | 								Basic.extend(runtime.getShimContainer().style, { | |||
|  | 									//position: 'relative',
 | |||
|  | 									top: '0px', | |||
|  | 									left: '0px', | |||
|  | 									width: img.width + 'px', | |||
|  | 									height: img.height + 'px' | |||
|  | 								}); | |||
|  | 
 | |||
|  | 								// some shims (Flash/SilverLight) reinitialize, if parent element is hidden, reordered or it's
 | |||
|  | 								// position type changes (in Gecko), but since we basically need this only in IEs 6/7 and
 | |||
|  | 								// sometimes 8 and they do not have this problem, we can comment this for now
 | |||
|  | 								/*tr.bind("RuntimeInit", function(e, runtime) { | |||
|  | 									tr.destroy(); | |||
|  | 									runtime.destroy(); | |||
|  | 									onResize.call(self); // re-feed our image data
 | |||
|  | 								});*/ | |||
|  | 
 | |||
|  | 								runtime = null; // release
 | |||
|  | 							}, 999); | |||
|  | 
 | |||
|  | 							runtime.exec.call(self, "ImageView", "display", this.result.uid, width, height); | |||
|  | 							img.destroy(); | |||
|  | 						}); | |||
|  | 
 | |||
|  | 						tr.transport(Encode.atob(dataUrl.substring(dataUrl.indexOf('base64,') + 7)), type, { | |||
|  | 							required_caps: { | |||
|  | 								display_media: true | |||
|  | 							}, | |||
|  | 							runtime_order: 'flash,silverlight', | |||
|  | 							container: el | |||
|  | 						}); | |||
|  | 					} | |||
|  | 				} | |||
|  | 
 | |||
|  | 				try { | |||
|  | 					if (!(el = Dom.get(el))) { | |||
|  | 						throw new x.DOMException(x.DOMException.INVALID_NODE_TYPE_ERR); | |||
|  | 					} | |||
|  | 
 | |||
|  | 					if (!this.size) { // only preloaded image objects can be used as source
 | |||
|  | 						throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); | |||
|  | 					} | |||
|  | 					 | |||
|  | 					// high-resolution images cannot be consistently handled across the runtimes
 | |||
|  | 					if (this.width > Image.MAX_RESIZE_WIDTH || this.height > Image.MAX_RESIZE_HEIGHT) { | |||
|  | 						//throw new x.ImageError(x.ImageError.MAX_RESOLUTION_ERR);
 | |||
|  | 					} | |||
|  | 
 | |||
|  | 					var imgCopy = new Image(); | |||
|  | 
 | |||
|  | 					imgCopy.bind("Resize", function() { | |||
|  | 						render.call(this, opts.type, opts.quality); | |||
|  | 					}); | |||
|  | 
 | |||
|  | 					imgCopy.bind("Load", function() { | |||
|  | 						imgCopy.downsize(opts); | |||
|  | 					}); | |||
|  | 
 | |||
|  | 					// if embedded thumb data is available and dimensions are big enough, use it
 | |||
|  | 					if (this.meta.thumb && this.meta.thumb.width >= opts.width && this.meta.thumb.height >= opts.height) { | |||
|  | 						imgCopy.load(this.meta.thumb.data); | |||
|  | 					} else { | |||
|  | 						imgCopy.clone(this, false); | |||
|  | 					} | |||
|  | 
 | |||
|  | 					return imgCopy; | |||
|  | 				} catch(ex) { | |||
|  | 					// for now simply trigger error event
 | |||
|  | 					this.trigger('error', ex.code); | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Properly destroys the image and frees resources in use. If any. Recommended way to dispose mOxie.Image object. | |||
|  | 
 | |||
|  | 			@method destroy | |||
|  | 			*/ | |||
|  | 			destroy: function() { | |||
|  | 				if (this.ruid) { | |||
|  | 					this.getRuntime().exec.call(this, 'Image', 'destroy'); | |||
|  | 					this.disconnectRuntime(); | |||
|  | 				} | |||
|  | 				this.unbindAll(); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 
 | |||
|  | 		// this is here, because in order to bind properly, we need uid, which is created above
 | |||
|  | 		this.handleEventProps(dispatches); | |||
|  | 
 | |||
|  | 		this.bind('Load Resize', function() { | |||
|  | 			_updateInfo.call(this); | |||
|  | 		}, 999); | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _updateInfo(info) { | |||
|  | 			if (!info) { | |||
|  | 				info = this.exec('Image', 'getInfo'); | |||
|  | 			} | |||
|  | 
 | |||
|  | 			this.size = info.size; | |||
|  | 			this.width = info.width; | |||
|  | 			this.height = info.height; | |||
|  | 			this.type = info.type; | |||
|  | 			this.meta = info.meta; | |||
|  | 
 | |||
|  | 			// update file name, only if empty
 | |||
|  | 			if (this.name === '') { | |||
|  | 				this.name = info.name; | |||
|  | 			} | |||
|  | 		} | |||
|  | 		 | |||
|  | 
 | |||
|  | 		function _load(src) { | |||
|  | 			var srcType = Basic.typeOf(src); | |||
|  | 
 | |||
|  | 			try { | |||
|  | 				// if source is Image
 | |||
|  | 				if (src instanceof Image) { | |||
|  | 					if (!src.size) { // only preloaded image objects can be used as source
 | |||
|  | 						throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); | |||
|  | 					} | |||
|  | 					_loadFromImage.apply(this, arguments); | |||
|  | 				} | |||
|  | 				// if source is o.Blob/o.File
 | |||
|  | 				else if (src instanceof Blob) { | |||
|  | 					if (!~Basic.inArray(src.type, ['image/jpeg', 'image/png'])) { | |||
|  | 						throw new x.ImageError(x.ImageError.WRONG_FORMAT); | |||
|  | 					} | |||
|  | 					_loadFromBlob.apply(this, arguments); | |||
|  | 				} | |||
|  | 				// if native blob/file
 | |||
|  | 				else if (Basic.inArray(srcType, ['blob', 'file']) !== -1) { | |||
|  | 					_load.call(this, new File(null, src), arguments[1]); | |||
|  | 				} | |||
|  | 				// if String
 | |||
|  | 				else if (srcType === 'string') { | |||
|  | 					// if dataUrl String
 | |||
|  | 					if (src.substr(0, 5) === 'data:') { | |||
|  | 						_load.call(this, new Blob(null, { data: src }), arguments[1]); | |||
|  | 					} | |||
|  | 					// else assume Url, either relative or absolute
 | |||
|  | 					else { | |||
|  | 						_loadFromUrl.apply(this, arguments); | |||
|  | 					} | |||
|  | 				} | |||
|  | 				// if source seems to be an img node
 | |||
|  | 				else if (srcType === 'node' && src.nodeName.toLowerCase() === 'img') { | |||
|  | 					_load.call(this, src.src, arguments[1]); | |||
|  | 				} | |||
|  | 				else { | |||
|  | 					throw new x.DOMException(x.DOMException.TYPE_MISMATCH_ERR); | |||
|  | 				} | |||
|  | 			} catch(ex) { | |||
|  | 				// for now simply trigger error event
 | |||
|  | 				this.trigger('error', ex.code); | |||
|  | 			} | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _loadFromImage(img, exact) { | |||
|  | 			var runtime = this.connectRuntime(img.ruid); | |||
|  | 			this.ruid = runtime.uid; | |||
|  | 			runtime.exec.call(this, 'Image', 'loadFromImage', img, (Basic.typeOf(exact) === 'undefined' ? true : exact)); | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _loadFromBlob(blob, options) { | |||
|  | 			var self = this; | |||
|  | 
 | |||
|  | 			self.name = blob.name || ''; | |||
|  | 
 | |||
|  | 			function exec(runtime) { | |||
|  | 				self.ruid = runtime.uid; | |||
|  | 				runtime.exec.call(self, 'Image', 'loadFromBlob', blob); | |||
|  | 			} | |||
|  | 
 | |||
|  | 			if (blob.isDetached()) { | |||
|  | 				this.bind('RuntimeInit', function(e, runtime) { | |||
|  | 					exec(runtime); | |||
|  | 				}); | |||
|  | 
 | |||
|  | 				// convert to object representation
 | |||
|  | 				if (options && typeof(options.required_caps) === 'string') { | |||
|  | 					options.required_caps = Runtime.parseCaps(options.required_caps); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				this.connectRuntime(Basic.extend({ | |||
|  | 					required_caps: { | |||
|  | 						access_image_binary: true, | |||
|  | 						resize_image: true | |||
|  | 					} | |||
|  | 				}, options)); | |||
|  | 			} else { | |||
|  | 				exec(this.connectRuntime(blob.ruid)); | |||
|  | 			} | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _loadFromUrl(url, options) { | |||
|  | 			var self = this, xhr; | |||
|  | 
 | |||
|  | 			xhr = new XMLHttpRequest(); | |||
|  | 
 | |||
|  | 			xhr.open('get', url); | |||
|  | 			xhr.responseType = 'blob'; | |||
|  | 
 | |||
|  | 			xhr.onprogress = function(e) { | |||
|  | 				self.trigger(e); | |||
|  | 			}; | |||
|  | 
 | |||
|  | 			xhr.onload = function() { | |||
|  | 				_loadFromBlob.call(self, xhr.response, true); | |||
|  | 			}; | |||
|  | 
 | |||
|  | 			xhr.onerror = function(e) { | |||
|  | 				self.trigger(e); | |||
|  | 			}; | |||
|  | 
 | |||
|  | 			xhr.onloadend = function() { | |||
|  | 				xhr.destroy(); | |||
|  | 			}; | |||
|  | 
 | |||
|  | 			xhr.bind('RuntimeError', function(e, err) { | |||
|  | 				self.trigger('RuntimeError', err); | |||
|  | 			}); | |||
|  | 
 | |||
|  | 			xhr.send(null, options); | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// virtual world will crash on you if image has a resolution higher than this:
 | |||
|  | 	Image.MAX_RESIZE_WIDTH = 8192; | |||
|  | 	Image.MAX_RESIZE_HEIGHT = 8192;  | |||
|  | 
 | |||
|  | 	Image.prototype = EventTarget.instance; | |||
|  | 
 | |||
|  | 	return Image; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html5/Runtime.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * Runtime.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /*global File:true */ | |||
|  | 
 | |||
|  | /** | |||
|  | Defines constructor for HTML5 runtime. | |||
|  | 
 | |||
|  | @class moxie/runtime/html5/Runtime | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html5/Runtime", [ | |||
|  | 	"moxie/core/utils/Basic", | |||
|  | 	"moxie/core/Exceptions", | |||
|  | 	"moxie/runtime/Runtime", | |||
|  | 	"moxie/core/utils/Env" | |||
|  | ], function(Basic, x, Runtime, Env) { | |||
|  | 	 | |||
|  | 	var type = "html5", extensions = {}; | |||
|  | 	 | |||
|  | 	function Html5Runtime(options) { | |||
|  | 		var I = this | |||
|  | 		, Test = Runtime.capTest | |||
|  | 		, True = Runtime.capTrue | |||
|  | 		; | |||
|  | 
 | |||
|  | 		var caps = Basic.extend({ | |||
|  | 				access_binary: Test(window.FileReader || window.File && window.File.getAsDataURL), | |||
|  | 				access_image_binary: function() { | |||
|  | 					return I.can('access_binary') && !!extensions.Image; | |||
|  | 				}, | |||
|  | 				display_media: Test(Env.can('create_canvas') || Env.can('use_data_uri_over32kb')), | |||
|  | 				do_cors: Test(window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()), | |||
|  | 				drag_and_drop: Test(function() { | |||
|  | 					// this comes directly from Modernizr: http://www.modernizr.com/
 | |||
|  | 					var div = document.createElement('div'); | |||
|  | 					// IE has support for drag and drop since version 5, but doesn't support dropping files from desktop
 | |||
|  | 					return (('draggable' in div) || ('ondragstart' in div && 'ondrop' in div)) &&  | |||
|  | 						(Env.browser !== 'IE' || Env.verComp(Env.version, 9, '>')); | |||
|  | 				}()), | |||
|  | 				filter_by_extension: Test(function() { // if you know how to feature-detect this, please suggest
 | |||
|  | 					return (Env.browser === 'Chrome' && Env.verComp(Env.version, 28, '>=')) ||  | |||
|  | 						(Env.browser === 'IE' && Env.verComp(Env.version, 10, '>=')) ||  | |||
|  | 						(Env.browser === 'Safari' && Env.verComp(Env.version, 7, '>=')); | |||
|  | 				}()), | |||
|  | 				return_response_headers: True, | |||
|  | 				return_response_type: function(responseType) { | |||
|  | 					if (responseType === 'json' && !!window.JSON) { // we can fake this one even if it's not supported
 | |||
|  | 						return true; | |||
|  | 					}  | |||
|  | 					return Env.can('return_response_type', responseType); | |||
|  | 				}, | |||
|  | 				return_status_code: True, | |||
|  | 				report_upload_progress: Test(window.XMLHttpRequest && new XMLHttpRequest().upload), | |||
|  | 				resize_image: function() { | |||
|  | 					return I.can('access_binary') && Env.can('create_canvas'); | |||
|  | 				}, | |||
|  | 				select_file: function() { | |||
|  | 					return Env.can('use_fileinput') && window.File; | |||
|  | 				}, | |||
|  | 				select_folder: function() { | |||
|  | 					return I.can('select_file') && Env.browser === 'Chrome' && Env.verComp(Env.version, 21, '>='); | |||
|  | 				}, | |||
|  | 				select_multiple: function() { | |||
|  | 					// it is buggy on Safari Windows and iOS
 | |||
|  | 					return I.can('select_file') && | |||
|  | 						!(Env.browser === 'Safari' && Env.os === 'Windows') && | |||
|  | 						!(Env.os === 'iOS' && Env.verComp(Env.osVersion, "7.0.0", '>') && Env.verComp(Env.osVersion, "8.0.0", '<')); | |||
|  | 				}, | |||
|  | 				send_binary_string: Test(window.XMLHttpRequest && (new XMLHttpRequest().sendAsBinary || (window.Uint8Array && window.ArrayBuffer))), | |||
|  | 				send_custom_headers: Test(window.XMLHttpRequest), | |||
|  | 				send_multipart: function() { | |||
|  | 					return !!(window.XMLHttpRequest && new XMLHttpRequest().upload && window.FormData) || I.can('send_binary_string'); | |||
|  | 				}, | |||
|  | 				slice_blob: Test(window.File && (File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice)), | |||
|  | 				stream_upload: function(){ | |||
|  | 					return I.can('slice_blob') && I.can('send_multipart'); | |||
|  | 				}, | |||
|  | 				summon_file_dialog: function() { // yeah... some dirty sniffing here...
 | |||
|  | 					return I.can('select_file') && ( | |||
|  | 						(Env.browser === 'Firefox' && Env.verComp(Env.version, 4, '>=')) || | |||
|  | 						(Env.browser === 'Opera' && Env.verComp(Env.version, 12, '>=')) || | |||
|  | 						(Env.browser === 'IE' && Env.verComp(Env.version, 10, '>=')) || | |||
|  | 						!!~Basic.inArray(Env.browser, ['Chrome', 'Safari']) | |||
|  | 					); | |||
|  | 				}, | |||
|  | 				upload_filesize: True | |||
|  | 			},  | |||
|  | 			arguments[2] | |||
|  | 		); | |||
|  | 
 | |||
|  | 		Runtime.call(this, options, (arguments[1] || type), caps); | |||
|  | 
 | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 
 | |||
|  | 			init : function() { | |||
|  | 				this.trigger("Init"); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			destroy: (function(destroy) { // extend default destroy method
 | |||
|  | 				return function() { | |||
|  | 					destroy.call(I); | |||
|  | 					destroy = I = null; | |||
|  | 				}; | |||
|  | 			}(this.destroy)) | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		Basic.extend(this.getShim(), extensions); | |||
|  | 	} | |||
|  | 
 | |||
|  | 	Runtime.addConstructor(type, Html5Runtime); | |||
|  | 
 | |||
|  | 	return extensions; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/core/utils/Events.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * Events.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | define('moxie/core/utils/Events', [ | |||
|  | 	'moxie/core/utils/Basic' | |||
|  | ], function(Basic) { | |||
|  | 	var eventhash = {}, uid = 'moxie_' + Basic.guid(); | |||
|  | 	 | |||
|  | 	// IE W3C like event funcs
 | |||
|  | 	function preventDefault() { | |||
|  | 		this.returnValue = false; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	function stopPropagation() { | |||
|  | 		this.cancelBubble = true; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	Adds an event handler to the specified object and store reference to the handler | |||
|  | 	in objects internal Plupload registry (@see removeEvent). | |||
|  | 	 | |||
|  | 	@method addEvent | |||
|  | 	@for Utils | |||
|  | 	@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} [key] that might be used to add specifity to the event record. | |||
|  | 	*/ | |||
|  | 	var addEvent = function(obj, name, callback, key) { | |||
|  | 		var func, events; | |||
|  | 					 | |||
|  | 		name = name.toLowerCase(); | |||
|  | 
 | |||
|  | 		// Add event listener
 | |||
|  | 		if (obj.addEventListener) { | |||
|  | 			func = callback; | |||
|  | 			 | |||
|  | 			obj.addEventListener(name, func, false); | |||
|  | 		} else if (obj.attachEvent) { | |||
|  | 			func = function() { | |||
|  | 				var evt = window.event; | |||
|  | 
 | |||
|  | 				if (!evt.target) { | |||
|  | 					evt.target = evt.srcElement; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				evt.preventDefault = preventDefault; | |||
|  | 				evt.stopPropagation = stopPropagation; | |||
|  | 
 | |||
|  | 				callback(evt); | |||
|  | 			}; | |||
|  | 
 | |||
|  | 			obj.attachEvent('on' + name, func); | |||
|  | 		} | |||
|  | 		 | |||
|  | 		// Log event handler to objects internal mOxie registry
 | |||
|  | 		if (!obj[uid]) { | |||
|  | 			obj[uid] = Basic.guid(); | |||
|  | 		} | |||
|  | 		 | |||
|  | 		if (!eventhash.hasOwnProperty(obj[uid])) { | |||
|  | 			eventhash[obj[uid]] = {}; | |||
|  | 		} | |||
|  | 		 | |||
|  | 		events = eventhash[obj[uid]]; | |||
|  | 		 | |||
|  | 		if (!events.hasOwnProperty(name)) { | |||
|  | 			events[name] = []; | |||
|  | 		} | |||
|  | 				 | |||
|  | 		events[name].push({ | |||
|  | 			func: func, | |||
|  | 			orig: callback, // store original callback for IE
 | |||
|  | 			key: key | |||
|  | 		}); | |||
|  | 	}; | |||
|  | 	 | |||
|  | 	 | |||
|  | 	/** | |||
|  | 	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} [callback] might be a callback or unique key to match. | |||
|  | 	*/ | |||
|  | 	var removeEvent = function(obj, name, callback) { | |||
|  | 		var type, undef; | |||
|  | 		 | |||
|  | 		name = name.toLowerCase(); | |||
|  | 		 | |||
|  | 		if (obj[uid] && eventhash[obj[uid]] && eventhash[obj[uid]][name]) { | |||
|  | 			type = eventhash[obj[uid]][name]; | |||
|  | 		} else { | |||
|  | 			return; | |||
|  | 		} | |||
|  | 			 | |||
|  | 		for (var i = type.length - 1; i >= 0; i--) { | |||
|  | 			// undefined or not, key should match
 | |||
|  | 			if (type[i].orig === callback || type[i].key === callback) { | |||
|  | 				if (obj.removeEventListener) { | |||
|  | 					obj.removeEventListener(name, type[i].func, false); | |||
|  | 				} else if (obj.detachEvent) { | |||
|  | 					obj.detachEvent('on'+name, type[i].func); | |||
|  | 				} | |||
|  | 				 | |||
|  | 				type[i].orig = null; | |||
|  | 				type[i].func = null; | |||
|  | 				type.splice(i, 1); | |||
|  | 				 | |||
|  | 				// If callback was passed we are done here, otherwise proceed
 | |||
|  | 				if (callback !== undef) { | |||
|  | 					break; | |||
|  | 				} | |||
|  | 			} | |||
|  | 		} | |||
|  | 		 | |||
|  | 		// If event array got empty, remove it
 | |||
|  | 		if (!type.length) { | |||
|  | 			delete eventhash[obj[uid]][name]; | |||
|  | 		} | |||
|  | 		 | |||
|  | 		// If mOxie registry has become empty, remove it
 | |||
|  | 		if (Basic.isEmptyObj(eventhash[obj[uid]])) { | |||
|  | 			delete eventhash[obj[uid]]; | |||
|  | 			 | |||
|  | 			// IE doesn't let you remove DOM object property with - delete
 | |||
|  | 			try { | |||
|  | 				delete obj[uid]; | |||
|  | 			} catch(e) { | |||
|  | 				obj[uid] = undef; | |||
|  | 			} | |||
|  | 		} | |||
|  | 	}; | |||
|  | 	 | |||
|  | 	 | |||
|  | 	/** | |||
|  | 	Remove all kind of events from the specified object | |||
|  | 	 | |||
|  | 	@method removeAllEvents | |||
|  | 	@static | |||
|  | 	@param {Object} obj DOM element to remove event listeners from. | |||
|  | 	@param {String} [key] unique key to match, when removing events. | |||
|  | 	*/ | |||
|  | 	var removeAllEvents = function(obj, key) {		 | |||
|  | 		if (!obj || !obj[uid]) { | |||
|  | 			return; | |||
|  | 		} | |||
|  | 		 | |||
|  | 		Basic.each(eventhash[obj[uid]], function(events, name) { | |||
|  | 			removeEvent(obj, name, key); | |||
|  | 		}); | |||
|  | 	}; | |||
|  | 
 | |||
|  | 	return { | |||
|  | 		addEvent: addEvent, | |||
|  | 		removeEvent: removeEvent, | |||
|  | 		removeAllEvents: removeAllEvents | |||
|  | 	}; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html5/file/FileInput.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * FileInput.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /** | |||
|  | @class moxie/runtime/html5/file/FileInput | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html5/file/FileInput", [ | |||
|  | 	"moxie/runtime/html5/Runtime", | |||
|  | 	"moxie/file/File", | |||
|  | 	"moxie/core/utils/Basic", | |||
|  | 	"moxie/core/utils/Dom", | |||
|  | 	"moxie/core/utils/Events", | |||
|  | 	"moxie/core/utils/Mime", | |||
|  | 	"moxie/core/utils/Env" | |||
|  | ], function(extensions, File, Basic, Dom, Events, Mime, Env) { | |||
|  | 	 | |||
|  | 	function FileInput() { | |||
|  | 		var _options; | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			init: function(options) { | |||
|  | 				var comp = this, I = comp.getRuntime(), input, shimContainer, mimes, browseButton, zIndex, top; | |||
|  | 
 | |||
|  | 				_options = options; | |||
|  | 
 | |||
|  | 				// figure out accept string
 | |||
|  | 				mimes = _options.accept.mimes || Mime.extList2mimes(_options.accept, I.can('filter_by_extension')); | |||
|  | 
 | |||
|  | 				shimContainer = I.getShimContainer(); | |||
|  | 
 | |||
|  | 				shimContainer.innerHTML = '<input id="' + I.uid +'" type="file" style="font-size:999px;opacity:0;"' + | |||
|  | 					(_options.multiple && I.can('select_multiple') ? 'multiple' : '') +  | |||
|  | 					(_options.directory && I.can('select_folder') ? 'webkitdirectory directory' : '') + // Chrome 11+
 | |||
|  | 					(mimes ? ' accept="' + mimes.join(',') + '"' : '') + ' />'; | |||
|  | 
 | |||
|  | 				input = Dom.get(I.uid); | |||
|  | 
 | |||
|  | 				// prepare file input to be placed underneath the browse_button element
 | |||
|  | 				Basic.extend(input.style, { | |||
|  | 					position: 'absolute', | |||
|  | 					top: 0, | |||
|  | 					left: 0, | |||
|  | 					width: '100%', | |||
|  | 					height: '100%' | |||
|  | 				}); | |||
|  | 
 | |||
|  | 
 | |||
|  | 				browseButton = Dom.get(_options.browse_button); | |||
|  | 
 | |||
|  | 				// Route click event to the input[type=file] element for browsers that support such behavior
 | |||
|  | 				if (I.can('summon_file_dialog')) { | |||
|  | 					if (Dom.getStyle(browseButton, 'position') === 'static') { | |||
|  | 						browseButton.style.position = 'relative'; | |||
|  | 					} | |||
|  | 
 | |||
|  | 					zIndex = parseInt(Dom.getStyle(browseButton, 'z-index'), 10) || 1; | |||
|  | 
 | |||
|  | 					browseButton.style.zIndex = zIndex; | |||
|  | 					shimContainer.style.zIndex = zIndex - 1; | |||
|  | 
 | |||
|  | 					Events.addEvent(browseButton, 'click', function(e) { | |||
|  | 						var input = Dom.get(I.uid); | |||
|  | 						if (input && !input.disabled) { // for some reason FF (up to 8.0.1 so far) lets to click disabled input[type=file]
 | |||
|  | 							input.click(); | |||
|  | 						} | |||
|  | 						e.preventDefault(); | |||
|  | 					}, comp.uid); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				/* Since we have to place input[type=file] on top of the browse_button for some browsers, | |||
|  | 				browse_button loses interactivity, so we restore it here */ | |||
|  | 				top = I.can('summon_file_dialog') ? browseButton : shimContainer; | |||
|  | 
 | |||
|  | 				Events.addEvent(top, 'mouseover', function() { | |||
|  | 					comp.trigger('mouseenter'); | |||
|  | 				}, comp.uid); | |||
|  | 
 | |||
|  | 				Events.addEvent(top, 'mouseout', function() { | |||
|  | 					comp.trigger('mouseleave'); | |||
|  | 				}, comp.uid); | |||
|  | 
 | |||
|  | 				Events.addEvent(top, 'mousedown', function() { | |||
|  | 					comp.trigger('mousedown'); | |||
|  | 				}, comp.uid); | |||
|  | 
 | |||
|  | 				Events.addEvent(Dom.get(_options.container), 'mouseup', function() { | |||
|  | 					comp.trigger('mouseup'); | |||
|  | 				}, comp.uid); | |||
|  | 
 | |||
|  | 
 | |||
|  | 				input.onchange = function onChange(e) { // there should be only one handler for this
 | |||
|  | 					comp.files = []; | |||
|  | 
 | |||
|  | 					Basic.each(this.files, function(file) { | |||
|  | 						var relativePath = ''; | |||
|  | 
 | |||
|  | 						if (_options.directory) { | |||
|  | 							// folders are represented by dots, filter them out (Chrome 11+)
 | |||
|  | 							if (file.name == ".") { | |||
|  | 								// if it looks like a folder...
 | |||
|  | 								return true; | |||
|  | 							} | |||
|  | 						} | |||
|  | 
 | |||
|  | 						if (file.webkitRelativePath) { | |||
|  | 							relativePath = '/' + file.webkitRelativePath.replace(/^\//, ''); | |||
|  | 						} | |||
|  | 						 | |||
|  | 						file = new File(I.uid, file); | |||
|  | 						file.relativePath = relativePath; | |||
|  | 
 | |||
|  | 						comp.files.push(file); | |||
|  | 					}); | |||
|  | 
 | |||
|  | 					// clearing the value enables the user to select the same file again if they want to
 | |||
|  | 					if (Env.browser !== 'IE' && Env.browser !== 'IEMobile') { | |||
|  | 						this.value = ''; | |||
|  | 					} else { | |||
|  | 						// in IE input[type="file"] is read-only so the only way to reset it is to re-insert it
 | |||
|  | 						var clone = this.cloneNode(true); | |||
|  | 						this.parentNode.replaceChild(clone, this); | |||
|  | 						clone.onchange = onChange; | |||
|  | 					} | |||
|  | 
 | |||
|  | 					if (comp.files.length) { | |||
|  | 						comp.trigger('change'); | |||
|  | 					} | |||
|  | 				}; | |||
|  | 
 | |||
|  | 				// ready event is perfectly asynchronous
 | |||
|  | 				comp.trigger({ | |||
|  | 					type: 'ready', | |||
|  | 					async: true | |||
|  | 				}); | |||
|  | 
 | |||
|  | 				shimContainer = null; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			disable: function(state) { | |||
|  | 				var I = this.getRuntime(), input; | |||
|  | 
 | |||
|  | 				if ((input = Dom.get(I.uid))) { | |||
|  | 					input.disabled = !!state; | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			destroy: function() { | |||
|  | 				var I = this.getRuntime() | |||
|  | 				, shim = I.getShim() | |||
|  | 				, shimContainer = I.getShimContainer() | |||
|  | 				; | |||
|  | 				 | |||
|  | 				Events.removeAllEvents(shimContainer, this.uid); | |||
|  | 				Events.removeAllEvents(_options && Dom.get(_options.container), this.uid); | |||
|  | 				Events.removeAllEvents(_options && Dom.get(_options.browse_button), this.uid); | |||
|  | 				 | |||
|  | 				if (shimContainer) { | |||
|  | 					shimContainer.innerHTML = ''; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				shim.removeInstance(this.uid); | |||
|  | 
 | |||
|  | 				_options = shimContainer = shim = null; | |||
|  | 			} | |||
|  | 		}); | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return (extensions.FileInput = FileInput); | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html5/file/Blob.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * Blob.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /** | |||
|  | @class moxie/runtime/html5/file/Blob | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html5/file/Blob", [ | |||
|  | 	"moxie/runtime/html5/Runtime", | |||
|  | 	"moxie/file/Blob" | |||
|  | ], function(extensions, Blob) { | |||
|  | 
 | |||
|  | 	function HTML5Blob() { | |||
|  | 		function w3cBlobSlice(blob, start, end) { | |||
|  | 			var blobSlice; | |||
|  | 
 | |||
|  | 			if (window.File.prototype.slice) { | |||
|  | 				try { | |||
|  | 					blob.slice();	// depricated version will throw WRONG_ARGUMENTS_ERR exception
 | |||
|  | 					return blob.slice(start, end); | |||
|  | 				} catch (e) { | |||
|  | 					// depricated slice method
 | |||
|  | 					return blob.slice(start, end - start); | |||
|  | 				} | |||
|  | 			// slice method got prefixed: https://bugzilla.mozilla.org/show_bug.cgi?id=649672
 | |||
|  | 			} else if ((blobSlice = window.File.prototype.webkitSlice || window.File.prototype.mozSlice)) { | |||
|  | 				return blobSlice.call(blob, start, end); | |||
|  | 			} else { | |||
|  | 				return null; // or throw some exception
 | |||
|  | 			} | |||
|  | 		} | |||
|  | 
 | |||
|  | 		this.slice = function() { | |||
|  | 			return new Blob(this.getRuntime().uid, w3cBlobSlice.apply(this, arguments)); | |||
|  | 		}; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return (extensions.Blob = HTML5Blob); | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html5/file/FileDrop.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * FileDrop.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /** | |||
|  | @class moxie/runtime/html5/file/FileDrop | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html5/file/FileDrop", [ | |||
|  | 	"moxie/runtime/html5/Runtime", | |||
|  | 	'moxie/file/File', | |||
|  | 	"moxie/core/utils/Basic", | |||
|  | 	"moxie/core/utils/Dom", | |||
|  | 	"moxie/core/utils/Events", | |||
|  | 	"moxie/core/utils/Mime" | |||
|  | ], function(extensions, File, Basic, Dom, Events, Mime) { | |||
|  | 	 | |||
|  | 	function FileDrop() { | |||
|  | 		var _files = [], _allowedExts = [], _options, _ruid; | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			init: function(options) { | |||
|  | 				var comp = this, dropZone; | |||
|  | 
 | |||
|  | 				_options = options; | |||
|  | 				_ruid = comp.ruid; // every dropped-in file should have a reference to the runtime
 | |||
|  | 				_allowedExts = _extractExts(_options.accept); | |||
|  | 				dropZone = _options.container; | |||
|  | 
 | |||
|  | 				Events.addEvent(dropZone, 'dragover', function(e) { | |||
|  | 					if (!_hasFiles(e)) { | |||
|  | 						return; | |||
|  | 					} | |||
|  | 					e.preventDefault(); | |||
|  | 					e.dataTransfer.dropEffect = 'copy'; | |||
|  | 				}, comp.uid); | |||
|  | 
 | |||
|  | 				Events.addEvent(dropZone, 'drop', function(e) { | |||
|  | 					if (!_hasFiles(e)) { | |||
|  | 						return; | |||
|  | 					} | |||
|  | 					e.preventDefault(); | |||
|  | 
 | |||
|  | 					_files = []; | |||
|  | 
 | |||
|  | 					// Chrome 21+ accepts folders via Drag'n'Drop
 | |||
|  | 					if (e.dataTransfer.items && e.dataTransfer.items[0].webkitGetAsEntry) { | |||
|  | 						_readItems(e.dataTransfer.items, function() { | |||
|  | 							comp.files = _files; | |||
|  | 							comp.trigger("drop"); | |||
|  | 						}); | |||
|  | 					} else { | |||
|  | 						Basic.each(e.dataTransfer.files, function(file) { | |||
|  | 							_addFile(file); | |||
|  | 						}); | |||
|  | 						comp.files = _files; | |||
|  | 						comp.trigger("drop"); | |||
|  | 					} | |||
|  | 				}, comp.uid); | |||
|  | 
 | |||
|  | 				Events.addEvent(dropZone, 'dragenter', function(e) { | |||
|  | 					comp.trigger("dragenter"); | |||
|  | 				}, comp.uid); | |||
|  | 
 | |||
|  | 				Events.addEvent(dropZone, 'dragleave', function(e) { | |||
|  | 					comp.trigger("dragleave"); | |||
|  | 				}, comp.uid); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			destroy: function() { | |||
|  | 				Events.removeAllEvents(_options && Dom.get(_options.container), this.uid); | |||
|  | 				_ruid = _files = _allowedExts = _options = null; | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _hasFiles(e) { | |||
|  | 			if (!e.dataTransfer || !e.dataTransfer.types) { // e.dataTransfer.files is not available in Gecko during dragover
 | |||
|  | 				return false; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			var types = Basic.toArray(e.dataTransfer.types || []); | |||
|  | 
 | |||
|  | 			return Basic.inArray("Files", types) !== -1 || | |||
|  | 				Basic.inArray("public.file-url", types) !== -1 || // Safari < 5
 | |||
|  | 				Basic.inArray("application/x-moz-file", types) !== -1 // Gecko < 1.9.2 (< Firefox 3.6)
 | |||
|  | 				; | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _addFile(file, relativePath) { | |||
|  | 			if (_isAcceptable(file)) { | |||
|  | 				var fileObj = new File(_ruid, file); | |||
|  | 				fileObj.relativePath = relativePath || ''; | |||
|  | 				_files.push(fileObj); | |||
|  | 			} | |||
|  | 		} | |||
|  | 
 | |||
|  | 		 | |||
|  | 		function _extractExts(accept) { | |||
|  | 			var exts = []; | |||
|  | 			for (var i = 0; i < accept.length; i++) { | |||
|  | 				[].push.apply(exts, accept[i].extensions.split(/\s*,\s*/)); | |||
|  | 			} | |||
|  | 			return Basic.inArray('*', exts) === -1 ? exts : []; | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _isAcceptable(file) { | |||
|  | 			if (!_allowedExts.length) { | |||
|  | 				return true; | |||
|  | 			} | |||
|  | 			var ext = Mime.getFileExtension(file.name); | |||
|  | 			return !ext || Basic.inArray(ext, _allowedExts) !== -1; | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _readItems(items, cb) { | |||
|  | 			var entries = []; | |||
|  | 			Basic.each(items, function(item) { | |||
|  | 				var entry = item.webkitGetAsEntry(); | |||
|  | 				// Address #998 (https://code.google.com/p/chromium/issues/detail?id=332579)
 | |||
|  | 				if (entry) { | |||
|  | 					// file() fails on OSX when the filename contains a special character (e.g. umlaut): see #61
 | |||
|  | 					if (entry.isFile) { | |||
|  | 						_addFile(item.getAsFile(), entry.fullPath); | |||
|  | 					} else { | |||
|  | 						entries.push(entry); | |||
|  | 					} | |||
|  | 				} | |||
|  | 			}); | |||
|  | 
 | |||
|  | 			if (entries.length) { | |||
|  | 				_readEntries(entries, cb); | |||
|  | 			} else { | |||
|  | 				cb(); | |||
|  | 			} | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _readEntries(entries, cb) { | |||
|  | 			var queue = []; | |||
|  | 			Basic.each(entries, function(entry) { | |||
|  | 				queue.push(function(cbcb) { | |||
|  | 					_readEntry(entry, cbcb); | |||
|  | 				}); | |||
|  | 			}); | |||
|  | 			Basic.inSeries(queue, function() { | |||
|  | 				cb(); | |||
|  | 			}); | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _readEntry(entry, cb) { | |||
|  | 			if (entry.isFile) { | |||
|  | 				entry.file(function(file) { | |||
|  | 					_addFile(file, entry.fullPath); | |||
|  | 					cb(); | |||
|  | 				}, function() { | |||
|  | 					// fire an error event maybe
 | |||
|  | 					cb(); | |||
|  | 				}); | |||
|  | 			} else if (entry.isDirectory) { | |||
|  | 				_readDirEntry(entry, cb); | |||
|  | 			} else { | |||
|  | 				cb(); // not file, not directory? what then?..
 | |||
|  | 			} | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _readDirEntry(dirEntry, cb) { | |||
|  | 			var entries = [], dirReader = dirEntry.createReader(); | |||
|  | 
 | |||
|  | 			// keep quering recursively till no more entries
 | |||
|  | 			function getEntries(cbcb) { | |||
|  | 				dirReader.readEntries(function(moreEntries) { | |||
|  | 					if (moreEntries.length) { | |||
|  | 						[].push.apply(entries, moreEntries); | |||
|  | 						getEntries(cbcb); | |||
|  | 					} else { | |||
|  | 						cbcb(); | |||
|  | 					} | |||
|  | 				}, cbcb); | |||
|  | 			} | |||
|  | 
 | |||
|  | 			// ...and you thought FileReader was crazy...
 | |||
|  | 			getEntries(function() { | |||
|  | 				_readEntries(entries, cb); | |||
|  | 			});  | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return (extensions.FileDrop = FileDrop); | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html5/file/FileReader.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * FileReader.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /** | |||
|  | @class moxie/runtime/html5/file/FileReader | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html5/file/FileReader", [ | |||
|  | 	"moxie/runtime/html5/Runtime", | |||
|  | 	"moxie/core/utils/Encode", | |||
|  | 	"moxie/core/utils/Basic" | |||
|  | ], function(extensions, Encode, Basic) { | |||
|  | 	 | |||
|  | 	function FileReader() { | |||
|  | 		var _fr, _convertToBinary = false; | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 
 | |||
|  | 			read: function(op, blob) { | |||
|  | 				var comp = this; | |||
|  | 
 | |||
|  | 				comp.result = ''; | |||
|  | 
 | |||
|  | 				_fr = new window.FileReader(); | |||
|  | 
 | |||
|  | 				_fr.addEventListener('progress', function(e) { | |||
|  | 					comp.trigger(e); | |||
|  | 				}); | |||
|  | 
 | |||
|  | 				_fr.addEventListener('load', function(e) { | |||
|  | 					comp.result = _convertToBinary ? _toBinary(_fr.result) : _fr.result; | |||
|  | 					comp.trigger(e); | |||
|  | 				}); | |||
|  | 
 | |||
|  | 				_fr.addEventListener('error', function(e) { | |||
|  | 					comp.trigger(e, _fr.error); | |||
|  | 				}); | |||
|  | 
 | |||
|  | 				_fr.addEventListener('loadend', function(e) { | |||
|  | 					_fr = null; | |||
|  | 					comp.trigger(e); | |||
|  | 				}); | |||
|  | 
 | |||
|  | 				if (Basic.typeOf(_fr[op]) === 'function') { | |||
|  | 					_convertToBinary = false; | |||
|  | 					_fr[op](blob.getSource()); | |||
|  | 				} else if (op === 'readAsBinaryString') { // readAsBinaryString is depricated in general and never existed in IE10+
 | |||
|  | 					_convertToBinary = true; | |||
|  | 					_fr.readAsDataURL(blob.getSource()); | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			abort: function() { | |||
|  | 				if (_fr) { | |||
|  | 					_fr.abort(); | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			destroy: function() { | |||
|  | 				_fr = null; | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		function _toBinary(str) { | |||
|  | 			return Encode.atob(str.substring(str.indexOf('base64,') + 7)); | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return (extensions.FileReader = FileReader); | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html5/xhr/XMLHttpRequest.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * XMLHttpRequest.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /*global ActiveXObject:true */ | |||
|  | 
 | |||
|  | /** | |||
|  | @class moxie/runtime/html5/xhr/XMLHttpRequest | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html5/xhr/XMLHttpRequest", [ | |||
|  | 	"moxie/runtime/html5/Runtime", | |||
|  | 	"moxie/core/utils/Basic", | |||
|  | 	"moxie/core/utils/Mime", | |||
|  | 	"moxie/core/utils/Url", | |||
|  | 	"moxie/file/File", | |||
|  | 	"moxie/file/Blob", | |||
|  | 	"moxie/xhr/FormData", | |||
|  | 	"moxie/core/Exceptions", | |||
|  | 	"moxie/core/utils/Env" | |||
|  | ], function(extensions, Basic, Mime, Url, File, Blob, FormData, x, Env) { | |||
|  | 	 | |||
|  | 	function XMLHttpRequest() { | |||
|  | 		var self = this | |||
|  | 		, _xhr | |||
|  | 		, _filename | |||
|  | 		; | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			send: function(meta, data) { | |||
|  | 				var target = this | |||
|  | 				, isGecko2_5_6 = (Env.browser === 'Mozilla' && Env.verComp(Env.version, 4, '>=') && Env.verComp(Env.version, 7, '<')) | |||
|  | 				, isAndroidBrowser = Env.browser === 'Android Browser' | |||
|  | 				, mustSendAsBinary = false | |||
|  | 				; | |||
|  | 
 | |||
|  | 				// extract file name
 | |||
|  | 				_filename = meta.url.replace(/^.+?\/([\w\-\.]+)$/, '$1').toLowerCase(); | |||
|  | 
 | |||
|  | 				_xhr = _getNativeXHR(); | |||
|  | 				_xhr.open(meta.method, meta.url, meta.async, meta.user, meta.password); | |||
|  | 
 | |||
|  | 
 | |||
|  | 				// prepare data to be sent
 | |||
|  | 				if (data instanceof Blob) { | |||
|  | 					if (data.isDetached()) { | |||
|  | 						mustSendAsBinary = true; | |||
|  | 					} | |||
|  | 					data = data.getSource(); | |||
|  | 				} else if (data instanceof FormData) { | |||
|  | 
 | |||
|  | 					if (data.hasBlob()) { | |||
|  | 						if (data.getBlob().isDetached()) { | |||
|  | 							data = _prepareMultipart.call(target, data); // _xhr must be instantiated and be in OPENED state
 | |||
|  | 							mustSendAsBinary = true; | |||
|  | 						} else if ((isGecko2_5_6 || isAndroidBrowser) && Basic.typeOf(data.getBlob().getSource()) === 'blob' && window.FileReader) { | |||
|  | 							// Gecko 2/5/6 can't send blob in FormData: https://bugzilla.mozilla.org/show_bug.cgi?id=649150
 | |||
|  | 							// Android browsers (default one and Dolphin) seem to have the same issue, see: #613
 | |||
|  | 							_preloadAndSend.call(target, meta, data); | |||
|  | 							return; // _preloadAndSend will reinvoke send() with transmutated FormData =%D
 | |||
|  | 						}	 | |||
|  | 					} | |||
|  | 
 | |||
|  | 					// transfer fields to real FormData
 | |||
|  | 					if (data instanceof FormData) { // if still a FormData, e.g. not mangled by _prepareMultipart()
 | |||
|  | 						var fd = new window.FormData(); | |||
|  | 						data.each(function(value, name) { | |||
|  | 							if (value instanceof Blob) { | |||
|  | 								fd.append(name, value.getSource()); | |||
|  | 							} else { | |||
|  | 								fd.append(name, value); | |||
|  | 							} | |||
|  | 						}); | |||
|  | 						data = fd; | |||
|  | 					} | |||
|  | 				} | |||
|  | 
 | |||
|  | 
 | |||
|  | 				// if XHR L2
 | |||
|  | 				if (_xhr.upload) { | |||
|  | 					if (meta.withCredentials) { | |||
|  | 						_xhr.withCredentials = true; | |||
|  | 					} | |||
|  | 
 | |||
|  | 					_xhr.addEventListener('load', function(e) { | |||
|  | 						target.trigger(e); | |||
|  | 					}); | |||
|  | 
 | |||
|  | 					_xhr.addEventListener('error', function(e) { | |||
|  | 						target.trigger(e); | |||
|  | 					}); | |||
|  | 
 | |||
|  | 					// additionally listen to progress events
 | |||
|  | 					_xhr.addEventListener('progress', function(e) { | |||
|  | 						target.trigger(e); | |||
|  | 					}); | |||
|  | 
 | |||
|  | 					_xhr.upload.addEventListener('progress', function(e) { | |||
|  | 						target.trigger({ | |||
|  | 							type: 'UploadProgress', | |||
|  | 							loaded: e.loaded, | |||
|  | 							total: e.total | |||
|  | 						}); | |||
|  | 					}); | |||
|  | 				// ... otherwise simulate XHR L2
 | |||
|  | 				} else { | |||
|  | 					_xhr.onreadystatechange = function onReadyStateChange() { | |||
|  | 						 | |||
|  | 						// fake Level 2 events
 | |||
|  | 						switch (_xhr.readyState) { | |||
|  | 							 | |||
|  | 							case 1: // XMLHttpRequest.OPENED
 | |||
|  | 								// readystatechanged is fired twice for OPENED state (in IE and Mozilla) - neu
 | |||
|  | 								break; | |||
|  | 							 | |||
|  | 							// looks like HEADERS_RECEIVED (state 2) is not reported in Opera (or it's old versions) - neu
 | |||
|  | 							case 2: // XMLHttpRequest.HEADERS_RECEIVED
 | |||
|  | 								break; | |||
|  | 								 | |||
|  | 							case 3: // XMLHttpRequest.LOADING 
 | |||
|  | 								// try to fire progress event for not XHR L2
 | |||
|  | 								var total, loaded; | |||
|  | 								 | |||
|  | 								try { | |||
|  | 									if (Url.hasSameOrigin(meta.url)) { // Content-Length not accessible for cross-domain on some browsers
 | |||
|  | 										total = _xhr.getResponseHeader('Content-Length') || 0; // old Safari throws an exception here
 | |||
|  | 									} | |||
|  | 
 | |||
|  | 									if (_xhr.responseText) { // responseText was introduced in IE7
 | |||
|  | 										loaded = _xhr.responseText.length; | |||
|  | 									} | |||
|  | 								} catch(ex) { | |||
|  | 									total = loaded = 0; | |||
|  | 								} | |||
|  | 
 | |||
|  | 								target.trigger({ | |||
|  | 									type: 'progress', | |||
|  | 									lengthComputable: !!total, | |||
|  | 									total: parseInt(total, 10), | |||
|  | 									loaded: loaded | |||
|  | 								}); | |||
|  | 								break; | |||
|  | 								 | |||
|  | 							case 4: // XMLHttpRequest.DONE
 | |||
|  | 								// release readystatechange handler (mostly for IE)
 | |||
|  | 								_xhr.onreadystatechange = function() {}; | |||
|  | 
 | |||
|  | 								// usually status 0 is returned when server is unreachable, but FF also fails to status 0 for 408 timeout
 | |||
|  | 								if (_xhr.status === 0) { | |||
|  | 									target.trigger('error'); | |||
|  | 								} else { | |||
|  | 									target.trigger('load'); | |||
|  | 								}							 | |||
|  | 								break; | |||
|  | 						} | |||
|  | 					}; | |||
|  | 				} | |||
|  | 				 | |||
|  | 
 | |||
|  | 				// set request headers
 | |||
|  | 				if (!Basic.isEmptyObj(meta.headers)) { | |||
|  | 					Basic.each(meta.headers, function(value, header) { | |||
|  | 						_xhr.setRequestHeader(header, value); | |||
|  | 					}); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// request response type
 | |||
|  | 				if ("" !== meta.responseType && 'responseType' in _xhr) { | |||
|  | 					if ('json' === meta.responseType && !Env.can('return_response_type', 'json')) { // we can fake this one
 | |||
|  | 						_xhr.responseType = 'text'; | |||
|  | 					} else { | |||
|  | 						_xhr.responseType = meta.responseType; | |||
|  | 					} | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// send ...
 | |||
|  | 				if (!mustSendAsBinary) { | |||
|  | 					_xhr.send(data); | |||
|  | 				} else { | |||
|  | 					if (_xhr.sendAsBinary) { // Gecko
 | |||
|  | 						_xhr.sendAsBinary(data); | |||
|  | 					} else { // other browsers having support for typed arrays
 | |||
|  | 						(function() { | |||
|  | 							// mimic Gecko's sendAsBinary
 | |||
|  | 							var ui8a = new Uint8Array(data.length); | |||
|  | 							for (var i = 0; i < data.length; i++) { | |||
|  | 								ui8a[i] = (data.charCodeAt(i) & 0xff); | |||
|  | 							} | |||
|  | 							_xhr.send(ui8a.buffer); | |||
|  | 						}()); | |||
|  | 					} | |||
|  | 				} | |||
|  | 
 | |||
|  | 				target.trigger('loadstart'); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			getStatus: function() { | |||
|  | 				// according to W3C spec it should return 0 for readyState < 3, but instead it throws an exception
 | |||
|  | 				try { | |||
|  | 					if (_xhr) { | |||
|  | 						return _xhr.status; | |||
|  | 					} | |||
|  | 				} catch(ex) {} | |||
|  | 				return 0; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			getResponse: function(responseType) { | |||
|  | 				var I = this.getRuntime(); | |||
|  | 
 | |||
|  | 				try { | |||
|  | 					switch (responseType) { | |||
|  | 						case 'blob': | |||
|  | 							var file = new File(I.uid, _xhr.response); | |||
|  | 							 | |||
|  | 							// try to extract file name from content-disposition if possible (might be - not, if CORS for example)	
 | |||
|  | 							var disposition = _xhr.getResponseHeader('Content-Disposition'); | |||
|  | 							if (disposition) { | |||
|  | 								// extract filename from response header if available
 | |||
|  | 								var match = disposition.match(/filename=([\'\"'])([^\1]+)\1/); | |||
|  | 								if (match) { | |||
|  | 									_filename = match[2]; | |||
|  | 								} | |||
|  | 							} | |||
|  | 							file.name = _filename; | |||
|  | 
 | |||
|  | 							// pre-webkit Opera doesn't set type property on the blob response
 | |||
|  | 							if (!file.type) { | |||
|  | 								file.type = Mime.getFileMime(_filename); | |||
|  | 							} | |||
|  | 							return file; | |||
|  | 
 | |||
|  | 						case 'json': | |||
|  | 							if (!Env.can('return_response_type', 'json')) { | |||
|  | 								return _xhr.status === 200 && !!window.JSON ? JSON.parse(_xhr.responseText) : null; | |||
|  | 							} | |||
|  | 							return _xhr.response; | |||
|  | 
 | |||
|  | 						case 'document': | |||
|  | 							return _getDocument(_xhr); | |||
|  | 
 | |||
|  | 						default: | |||
|  | 							return _xhr.responseText !== '' ? _xhr.responseText : null; // against the specs, but for consistency across the runtimes
 | |||
|  | 					} | |||
|  | 				} catch(ex) { | |||
|  | 					return null; | |||
|  | 				}				 | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			getAllResponseHeaders: function() { | |||
|  | 				try { | |||
|  | 					return _xhr.getAllResponseHeaders(); | |||
|  | 				} catch(ex) {} | |||
|  | 				return ''; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			abort: function() { | |||
|  | 				if (_xhr) { | |||
|  | 					_xhr.abort(); | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			destroy: function() { | |||
|  | 				self = _filename = null; | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 
 | |||
|  | 		// here we go... ugly fix for ugly bug
 | |||
|  | 		function _preloadAndSend(meta, data) { | |||
|  | 			var target = this, blob, fr; | |||
|  | 				 | |||
|  | 			// get original blob
 | |||
|  | 			blob = data.getBlob().getSource(); | |||
|  | 			 | |||
|  | 			// preload blob in memory to be sent as binary string
 | |||
|  | 			fr = new window.FileReader(); | |||
|  | 			fr.onload = function() { | |||
|  | 				// overwrite original blob
 | |||
|  | 				data.append(data.getBlobName(), new Blob(null, { | |||
|  | 					type: blob.type, | |||
|  | 					data: fr.result | |||
|  | 				})); | |||
|  | 				// invoke send operation again
 | |||
|  | 				self.send.call(target, meta, data); | |||
|  | 			}; | |||
|  | 			fr.readAsBinaryString(blob); | |||
|  | 		} | |||
|  | 
 | |||
|  | 		 | |||
|  | 		function _getNativeXHR() { | |||
|  | 			if (window.XMLHttpRequest && !(Env.browser === 'IE' && Env.verComp(Env.version, 8, '<'))) { // IE7 has native XHR but it's buggy
 | |||
|  | 				return new window.XMLHttpRequest(); | |||
|  | 			} else { | |||
|  | 				return (function() { | |||
|  | 					var progIDs = ['Msxml2.XMLHTTP.6.0', 'Microsoft.XMLHTTP']; // if 6.0 available, use it, otherwise failback to default 3.0
 | |||
|  | 					for (var i = 0; i < progIDs.length; i++) { | |||
|  | 						try { | |||
|  | 							return new ActiveXObject(progIDs[i]); | |||
|  | 						} catch (ex) {} | |||
|  | 					} | |||
|  | 				})(); | |||
|  | 			} | |||
|  | 		} | |||
|  | 		 | |||
|  | 		// @credits Sergey Ilinsky	(http://www.ilinsky.com/)
 | |||
|  | 		function _getDocument(xhr) { | |||
|  | 			var rXML = xhr.responseXML; | |||
|  | 			var rText = xhr.responseText; | |||
|  | 			 | |||
|  | 			// Try parsing responseText (@see: http://www.ilinsky.com/articles/XMLHttpRequest/#bugs-ie-responseXML-content-type)
 | |||
|  | 			if (Env.browser === 'IE' && rText && rXML && !rXML.documentElement && /[^\/]+\/[^\+]+\+xml/.test(xhr.getResponseHeader("Content-Type"))) { | |||
|  | 				rXML = new window.ActiveXObject("Microsoft.XMLDOM"); | |||
|  | 				rXML.async = false; | |||
|  | 				rXML.validateOnParse = false; | |||
|  | 				rXML.loadXML(rText); | |||
|  | 			} | |||
|  | 	 | |||
|  | 			// Check if there is no error in document
 | |||
|  | 			if (rXML) { | |||
|  | 				if ((Env.browser === 'IE' && rXML.parseError !== 0) || !rXML.documentElement || rXML.documentElement.tagName === "parsererror") { | |||
|  | 					return null; | |||
|  | 				} | |||
|  | 			} | |||
|  | 			return rXML; | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _prepareMultipart(fd) { | |||
|  | 			var boundary = '----moxieboundary' + new Date().getTime() | |||
|  | 			, dashdash = '--' | |||
|  | 			, crlf = '\r\n' | |||
|  | 			, multipart = '' | |||
|  | 			, I = this.getRuntime() | |||
|  | 			; | |||
|  | 
 | |||
|  | 			if (!I.can('send_binary_string')) { | |||
|  | 				throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR); | |||
|  | 			} | |||
|  | 
 | |||
|  | 			_xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary); | |||
|  | 
 | |||
|  | 			// append multipart parameters
 | |||
|  | 			fd.each(function(value, name) { | |||
|  | 				// Firefox 3.6 failed to convert multibyte characters to UTF-8 in sendAsBinary(), 
 | |||
|  | 				// so we try it here ourselves with: unescape(encodeURIComponent(value))
 | |||
|  | 				if (value instanceof Blob) { | |||
|  | 					// Build RFC2388 blob
 | |||
|  | 					multipart += dashdash + boundary + crlf + | |||
|  | 						'Content-Disposition: form-data; name="' + name + '"; filename="' + unescape(encodeURIComponent(value.name || 'blob')) + '"' + crlf + | |||
|  | 						'Content-Type: ' + (value.type || 'application/octet-stream') + crlf + crlf + | |||
|  | 						value.getSource() + crlf; | |||
|  | 				} else { | |||
|  | 					multipart += dashdash + boundary + crlf + | |||
|  | 						'Content-Disposition: form-data; name="' + name + '"' + crlf + crlf + | |||
|  | 						unescape(encodeURIComponent(value)) + crlf; | |||
|  | 				} | |||
|  | 			}); | |||
|  | 
 | |||
|  | 			multipart += dashdash + boundary + dashdash + crlf; | |||
|  | 
 | |||
|  | 			return multipart; | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return (extensions.XMLHttpRequest = XMLHttpRequest); | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html5/utils/BinaryReader.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * BinaryReader.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /** | |||
|  | @class moxie/runtime/html5/utils/BinaryReader | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html5/utils/BinaryReader", [ | |||
|  | 	"moxie/core/utils/Basic" | |||
|  | ], function(Basic) { | |||
|  | 
 | |||
|  | 	 | |||
|  | 	function BinaryReader(data) { | |||
|  | 		if (data instanceof ArrayBuffer) { | |||
|  | 			ArrayBufferReader.apply(this, arguments); | |||
|  | 		} else { | |||
|  | 			UTF16StringReader.apply(this, arguments); | |||
|  | 		} | |||
|  | 	} | |||
|  | 	  | |||
|  | 
 | |||
|  | 	Basic.extend(BinaryReader.prototype, { | |||
|  | 		 | |||
|  | 		littleEndian: false, | |||
|  | 
 | |||
|  | 
 | |||
|  | 		read: function(idx, size) { | |||
|  | 			var sum, mv, i; | |||
|  | 
 | |||
|  | 			if (idx + size > this.length()) { | |||
|  | 				throw new Error("You are trying to read outside the source boundaries."); | |||
|  | 			} | |||
|  | 			 | |||
|  | 			mv = this.littleEndian  | |||
|  | 				? 0  | |||
|  | 				: -8 * (size - 1) | |||
|  | 			; | |||
|  | 
 | |||
|  | 			for (i = 0, sum = 0; i < size; i++) { | |||
|  | 				sum |= (this.readByteAt(idx + i) << Math.abs(mv + i*8)); | |||
|  | 			} | |||
|  | 			return sum; | |||
|  | 		}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 		write: function(idx, num, size) { | |||
|  | 			var mv, i, str = ''; | |||
|  | 
 | |||
|  | 			if (idx > this.length()) { | |||
|  | 				throw new Error("You are trying to write outside the source boundaries."); | |||
|  | 			} | |||
|  | 
 | |||
|  | 			mv = this.littleEndian  | |||
|  | 				? 0  | |||
|  | 				: -8 * (size - 1) | |||
|  | 			; | |||
|  | 
 | |||
|  | 			for (i = 0; i < size; i++) { | |||
|  | 				this.writeByteAt(idx + i, (num >> Math.abs(mv + i*8)) & 255); | |||
|  | 			} | |||
|  | 		}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 		BYTE: function(idx) { | |||
|  | 			return this.read(idx, 1); | |||
|  | 		}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 		SHORT: function(idx) { | |||
|  | 			return this.read(idx, 2); | |||
|  | 		}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 		LONG: function(idx) { | |||
|  | 			return this.read(idx, 4); | |||
|  | 		}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 		SLONG: function(idx) { // 2's complement notation
 | |||
|  | 			var num = this.read(idx, 4); | |||
|  | 			return (num > 2147483647 ? num - 4294967296 : num); | |||
|  | 		}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 		CHAR: function(idx) { | |||
|  | 			return String.fromCharCode(this.read(idx, 1)); | |||
|  | 		}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 		STRING: function(idx, count) { | |||
|  | 			return this.asArray('CHAR', idx, count).join(''); | |||
|  | 		}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 		asArray: function(type, idx, count) { | |||
|  | 			var values = []; | |||
|  | 
 | |||
|  | 			for (var i = 0; i < count; i++) { | |||
|  | 				values[i] = this[type](idx + i); | |||
|  | 			} | |||
|  | 			return values; | |||
|  | 		} | |||
|  | 	}); | |||
|  | 
 | |||
|  | 
 | |||
|  | 	function ArrayBufferReader(data) { | |||
|  | 		var _dv = new DataView(data); | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			 | |||
|  | 			readByteAt: function(idx) { | |||
|  | 				return _dv.getUint8(idx); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			writeByteAt: function(idx, value) { | |||
|  | 				_dv.setUint8(idx, value); | |||
|  | 			}, | |||
|  | 			 | |||
|  | 
 | |||
|  | 			SEGMENT: function(idx, size, value) { | |||
|  | 				switch (arguments.length) { | |||
|  | 					case 2: | |||
|  | 						return data.slice(idx, idx + size); | |||
|  | 
 | |||
|  | 					case 1: | |||
|  | 						return data.slice(idx); | |||
|  | 
 | |||
|  | 					case 3: | |||
|  | 						if (value === null) { | |||
|  | 							value = new ArrayBuffer(); | |||
|  | 						} | |||
|  | 
 | |||
|  | 						if (value instanceof ArrayBuffer) {					 | |||
|  | 							var arr = new Uint8Array(this.length() - size + value.byteLength); | |||
|  | 							if (idx > 0) { | |||
|  | 								arr.set(new Uint8Array(data.slice(0, idx)), 0); | |||
|  | 							} | |||
|  | 							arr.set(new Uint8Array(value), idx); | |||
|  | 							arr.set(new Uint8Array(data.slice(idx + size)), idx + value.byteLength); | |||
|  | 
 | |||
|  | 							this.clear(); | |||
|  | 							data = arr.buffer; | |||
|  | 							_dv = new DataView(data); | |||
|  | 							break; | |||
|  | 						} | |||
|  | 
 | |||
|  | 					default: return data; | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			length: function() { | |||
|  | 				return data ? data.byteLength : 0; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			clear: function() { | |||
|  | 				_dv = data = null; | |||
|  | 			} | |||
|  | 		}); | |||
|  | 	} | |||
|  | 
 | |||
|  | 
 | |||
|  | 	function UTF16StringReader(data) { | |||
|  | 		Basic.extend(this, { | |||
|  | 			 | |||
|  | 			readByteAt: function(idx) { | |||
|  | 				return data.charCodeAt(idx); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			writeByteAt: function(idx, value) { | |||
|  | 				putstr(String.fromCharCode(value), idx, 1); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			SEGMENT: function(idx, length, segment) { | |||
|  | 				switch (arguments.length) { | |||
|  | 					case 1: | |||
|  | 						return data.substr(idx); | |||
|  | 					case 2: | |||
|  | 						return data.substr(idx, length); | |||
|  | 					case 3: | |||
|  | 						putstr(segment !== null ? segment : '', idx, length); | |||
|  | 						break; | |||
|  | 					default: return data; | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			length: function() { | |||
|  | 				return data ? data.length : 0; | |||
|  | 			},  | |||
|  | 
 | |||
|  | 			clear: function() { | |||
|  | 				data = null; | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function putstr(segment, idx, length) { | |||
|  | 			length = arguments.length === 3 ? length : data.length - idx - 1; | |||
|  | 			data = data.substr(0, idx) + segment + data.substr(length + idx); | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 
 | |||
|  | 	return BinaryReader; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html5/image/JPEGHeaders.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * JPEGHeaders.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  |   | |||
|  | /** | |||
|  | @class moxie/runtime/html5/image/JPEGHeaders | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html5/image/JPEGHeaders", [ | |||
|  | 	"moxie/runtime/html5/utils/BinaryReader", | |||
|  | 	"moxie/core/Exceptions" | |||
|  | ], function(BinaryReader, x) { | |||
|  | 	 | |||
|  | 	return function JPEGHeaders(data) { | |||
|  | 		var headers = [], _br, idx, marker, length = 0; | |||
|  | 
 | |||
|  | 		_br = new BinaryReader(data); | |||
|  | 
 | |||
|  | 		// Check if data is jpeg
 | |||
|  | 		if (_br.SHORT(0) !== 0xFFD8) { | |||
|  | 			_br.clear(); | |||
|  | 			throw new x.ImageError(x.ImageError.WRONG_FORMAT); | |||
|  | 		} | |||
|  | 
 | |||
|  | 		idx = 2; | |||
|  | 
 | |||
|  | 		while (idx <= _br.length()) { | |||
|  | 			marker = _br.SHORT(idx); | |||
|  | 
 | |||
|  | 			// omit RST (restart) markers
 | |||
|  | 			if (marker >= 0xFFD0 && marker <= 0xFFD7) { | |||
|  | 				idx += 2; | |||
|  | 				continue; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			// no headers allowed after SOS marker
 | |||
|  | 			if (marker === 0xFFDA || marker === 0xFFD9) { | |||
|  | 				break; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			length = _br.SHORT(idx + 2) + 2; | |||
|  | 
 | |||
|  | 			// APPn marker detected
 | |||
|  | 			if (marker >= 0xFFE1 && marker <= 0xFFEF) { | |||
|  | 				headers.push({ | |||
|  | 					hex: marker, | |||
|  | 					name: 'APP' + (marker & 0x000F), | |||
|  | 					start: idx, | |||
|  | 					length: length, | |||
|  | 					segment: _br.SEGMENT(idx, length) | |||
|  | 				}); | |||
|  | 			} | |||
|  | 
 | |||
|  | 			idx += length; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		_br.clear(); | |||
|  | 
 | |||
|  | 		return { | |||
|  | 			headers: headers, | |||
|  | 
 | |||
|  | 			restore: function(data) { | |||
|  | 				var max, i, br; | |||
|  | 
 | |||
|  | 				br = new BinaryReader(data); | |||
|  | 
 | |||
|  | 				idx = br.SHORT(2) == 0xFFE0 ? 4 + br.SHORT(4) : 2; | |||
|  | 
 | |||
|  | 				for (i = 0, max = headers.length; i < max; i++) { | |||
|  | 					br.SEGMENT(idx, 0, headers[i].segment); | |||
|  | 					idx += headers[i].length; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				data = br.SEGMENT(); | |||
|  | 				br.clear(); | |||
|  | 				return data; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			strip: function(data) { | |||
|  | 				var br, headers, jpegHeaders, i; | |||
|  | 
 | |||
|  | 				jpegHeaders = new JPEGHeaders(data); | |||
|  | 				headers = jpegHeaders.headers; | |||
|  | 				jpegHeaders.purge(); | |||
|  | 
 | |||
|  | 				br = new BinaryReader(data); | |||
|  | 
 | |||
|  | 				i = headers.length; | |||
|  | 				while (i--) { | |||
|  | 					br.SEGMENT(headers[i].start, headers[i].length, ''); | |||
|  | 				} | |||
|  | 				 | |||
|  | 				data = br.SEGMENT(); | |||
|  | 				br.clear(); | |||
|  | 				return data; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			get: function(name) { | |||
|  | 				var array = []; | |||
|  | 
 | |||
|  | 				for (var i = 0, max = headers.length; i < max; i++) { | |||
|  | 					if (headers[i].name === name.toUpperCase()) { | |||
|  | 						array.push(headers[i].segment); | |||
|  | 					} | |||
|  | 				} | |||
|  | 				return array; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			set: function(name, segment) { | |||
|  | 				var array = [], i, ii, max; | |||
|  | 
 | |||
|  | 				if (typeof(segment) === 'string') { | |||
|  | 					array.push(segment); | |||
|  | 				} else { | |||
|  | 					array = segment; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				for (i = ii = 0, max = headers.length; i < max; i++) { | |||
|  | 					if (headers[i].name === name.toUpperCase()) { | |||
|  | 						headers[i].segment = array[ii]; | |||
|  | 						headers[i].length = array[ii].length; | |||
|  | 						ii++; | |||
|  | 					} | |||
|  | 					if (ii >= array.length) { | |||
|  | 						break; | |||
|  | 					} | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			purge: function() { | |||
|  | 				this.headers = headers = []; | |||
|  | 			} | |||
|  | 		}; | |||
|  | 	}; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html5/image/ExifParser.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * ExifParser.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /** | |||
|  | @class moxie/runtime/html5/image/ExifParser | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html5/image/ExifParser", [ | |||
|  | 	"moxie/core/utils/Basic", | |||
|  | 	"moxie/runtime/html5/utils/BinaryReader", | |||
|  | 	"moxie/core/Exceptions" | |||
|  | ], function(Basic, BinaryReader, x) { | |||
|  | 	 | |||
|  | 	function ExifParser(data) { | |||
|  | 		var __super__, tags, tagDescs, offsets, idx, Tiff; | |||
|  | 		 | |||
|  | 		BinaryReader.call(this, data); | |||
|  | 
 | |||
|  | 		tags = { | |||
|  | 			tiff: { | |||
|  | 				/* | |||
|  | 				The image orientation viewed in terms of rows and columns. | |||
|  | 
 | |||
|  | 				1 = The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side. | |||
|  | 				2 = The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side. | |||
|  | 				3 = The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side. | |||
|  | 				4 = The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side. | |||
|  | 				5 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual top. | |||
|  | 				6 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual top. | |||
|  | 				7 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom. | |||
|  | 				8 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom. | |||
|  | 				*/ | |||
|  | 				0x0112: 'Orientation', | |||
|  | 				0x010E: 'ImageDescription', | |||
|  | 				0x010F: 'Make', | |||
|  | 				0x0110: 'Model', | |||
|  | 				0x0131: 'Software', | |||
|  | 				0x8769: 'ExifIFDPointer', | |||
|  | 				0x8825:	'GPSInfoIFDPointer' | |||
|  | 			}, | |||
|  | 			exif: { | |||
|  | 				0x9000: 'ExifVersion', | |||
|  | 				0xA001: 'ColorSpace', | |||
|  | 				0xA002: 'PixelXDimension', | |||
|  | 				0xA003: 'PixelYDimension', | |||
|  | 				0x9003: 'DateTimeOriginal', | |||
|  | 				0x829A: 'ExposureTime', | |||
|  | 				0x829D: 'FNumber', | |||
|  | 				0x8827: 'ISOSpeedRatings', | |||
|  | 				0x9201: 'ShutterSpeedValue', | |||
|  | 				0x9202: 'ApertureValue'	, | |||
|  | 				0x9207: 'MeteringMode', | |||
|  | 				0x9208: 'LightSource', | |||
|  | 				0x9209: 'Flash', | |||
|  | 				0x920A: 'FocalLength', | |||
|  | 				0xA402: 'ExposureMode', | |||
|  | 				0xA403: 'WhiteBalance', | |||
|  | 				0xA406: 'SceneCaptureType', | |||
|  | 				0xA404: 'DigitalZoomRatio', | |||
|  | 				0xA408: 'Contrast', | |||
|  | 				0xA409: 'Saturation', | |||
|  | 				0xA40A: 'Sharpness' | |||
|  | 			}, | |||
|  | 			gps: { | |||
|  | 				0x0000: 'GPSVersionID', | |||
|  | 				0x0001: 'GPSLatitudeRef', | |||
|  | 				0x0002: 'GPSLatitude', | |||
|  | 				0x0003: 'GPSLongitudeRef', | |||
|  | 				0x0004: 'GPSLongitude' | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			thumb: { | |||
|  | 				0x0201: 'JPEGInterchangeFormat', | |||
|  | 				0x0202: 'JPEGInterchangeFormatLength' | |||
|  | 			} | |||
|  | 		}; | |||
|  | 
 | |||
|  | 		tagDescs = { | |||
|  | 			'ColorSpace': { | |||
|  | 				1: 'sRGB', | |||
|  | 				0: 'Uncalibrated' | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			'MeteringMode': { | |||
|  | 				0: 'Unknown', | |||
|  | 				1: 'Average', | |||
|  | 				2: 'CenterWeightedAverage', | |||
|  | 				3: 'Spot', | |||
|  | 				4: 'MultiSpot', | |||
|  | 				5: 'Pattern', | |||
|  | 				6: 'Partial', | |||
|  | 				255: 'Other' | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			'LightSource': { | |||
|  | 				1: 'Daylight', | |||
|  | 				2: 'Fliorescent', | |||
|  | 				3: 'Tungsten', | |||
|  | 				4: 'Flash', | |||
|  | 				9: 'Fine weather', | |||
|  | 				10: 'Cloudy weather', | |||
|  | 				11: 'Shade', | |||
|  | 				12: 'Daylight fluorescent (D 5700 - 7100K)', | |||
|  | 				13: 'Day white fluorescent (N 4600 -5400K)', | |||
|  | 				14: 'Cool white fluorescent (W 3900 - 4500K)', | |||
|  | 				15: 'White fluorescent (WW 3200 - 3700K)', | |||
|  | 				17: 'Standard light A', | |||
|  | 				18: 'Standard light B', | |||
|  | 				19: 'Standard light C', | |||
|  | 				20: 'D55', | |||
|  | 				21: 'D65', | |||
|  | 				22: 'D75', | |||
|  | 				23: 'D50', | |||
|  | 				24: 'ISO studio tungsten', | |||
|  | 				255: 'Other' | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			'Flash': { | |||
|  | 				0x0000: 'Flash did not fire', | |||
|  | 				0x0001: 'Flash fired', | |||
|  | 				0x0005: 'Strobe return light not detected', | |||
|  | 				0x0007: 'Strobe return light detected', | |||
|  | 				0x0009: 'Flash fired, compulsory flash mode', | |||
|  | 				0x000D: 'Flash fired, compulsory flash mode, return light not detected', | |||
|  | 				0x000F: 'Flash fired, compulsory flash mode, return light detected', | |||
|  | 				0x0010: 'Flash did not fire, compulsory flash mode', | |||
|  | 				0x0018: 'Flash did not fire, auto mode', | |||
|  | 				0x0019: 'Flash fired, auto mode', | |||
|  | 				0x001D: 'Flash fired, auto mode, return light not detected', | |||
|  | 				0x001F: 'Flash fired, auto mode, return light detected', | |||
|  | 				0x0020: 'No flash function', | |||
|  | 				0x0041: 'Flash fired, red-eye reduction mode', | |||
|  | 				0x0045: 'Flash fired, red-eye reduction mode, return light not detected', | |||
|  | 				0x0047: 'Flash fired, red-eye reduction mode, return light detected', | |||
|  | 				0x0049: 'Flash fired, compulsory flash mode, red-eye reduction mode', | |||
|  | 				0x004D: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected', | |||
|  | 				0x004F: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected', | |||
|  | 				0x0059: 'Flash fired, auto mode, red-eye reduction mode', | |||
|  | 				0x005D: 'Flash fired, auto mode, return light not detected, red-eye reduction mode', | |||
|  | 				0x005F: 'Flash fired, auto mode, return light detected, red-eye reduction mode' | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			'ExposureMode': { | |||
|  | 				0: 'Auto exposure', | |||
|  | 				1: 'Manual exposure', | |||
|  | 				2: 'Auto bracket' | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			'WhiteBalance': { | |||
|  | 				0: 'Auto white balance', | |||
|  | 				1: 'Manual white balance' | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			'SceneCaptureType': { | |||
|  | 				0: 'Standard', | |||
|  | 				1: 'Landscape', | |||
|  | 				2: 'Portrait', | |||
|  | 				3: 'Night scene' | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			'Contrast': { | |||
|  | 				0: 'Normal', | |||
|  | 				1: 'Soft', | |||
|  | 				2: 'Hard' | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			'Saturation': { | |||
|  | 				0: 'Normal', | |||
|  | 				1: 'Low saturation', | |||
|  | 				2: 'High saturation' | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			'Sharpness': { | |||
|  | 				0: 'Normal', | |||
|  | 				1: 'Soft', | |||
|  | 				2: 'Hard' | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			// GPS related
 | |||
|  | 			'GPSLatitudeRef': { | |||
|  | 				N: 'North latitude', | |||
|  | 				S: 'South latitude' | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			'GPSLongitudeRef': { | |||
|  | 				E: 'East longitude', | |||
|  | 				W: 'West longitude' | |||
|  | 			} | |||
|  | 		}; | |||
|  | 
 | |||
|  | 		offsets = { | |||
|  | 			tiffHeader: 10 | |||
|  | 		}; | |||
|  | 		 | |||
|  | 		idx = offsets.tiffHeader; | |||
|  | 
 | |||
|  | 		__super__ = { | |||
|  | 			clear: this.clear | |||
|  | 		}; | |||
|  | 
 | |||
|  | 		// Public functions
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			 | |||
|  | 			read: function() { | |||
|  | 				try { | |||
|  | 					return ExifParser.prototype.read.apply(this, arguments); | |||
|  | 				} catch (ex) { | |||
|  | 					throw new x.ImageError(x.ImageError.INVALID_META_ERR); | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			write: function() { | |||
|  | 				try { | |||
|  | 					return ExifParser.prototype.write.apply(this, arguments); | |||
|  | 				} catch (ex) { | |||
|  | 					throw new x.ImageError(x.ImageError.INVALID_META_ERR); | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			UNDEFINED: function() { | |||
|  | 				return this.BYTE.apply(this, arguments); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			RATIONAL: function(idx) { | |||
|  | 				return this.LONG(idx) / this.LONG(idx + 4) | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			SRATIONAL: function(idx) { | |||
|  | 				return this.SLONG(idx) / this.SLONG(idx + 4) | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			ASCII: function(idx) { | |||
|  | 				return this.CHAR(idx); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			TIFF: function() { | |||
|  | 				return Tiff || null; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			EXIF: function() { | |||
|  | 				var Exif = null; | |||
|  | 
 | |||
|  | 				if (offsets.exifIFD) { | |||
|  | 					try { | |||
|  | 						Exif = extractTags.call(this, offsets.exifIFD, tags.exif); | |||
|  | 					} catch(ex) { | |||
|  | 						return null; | |||
|  | 					} | |||
|  | 
 | |||
|  | 					// Fix formatting of some tags
 | |||
|  | 					if (Exif.ExifVersion && Basic.typeOf(Exif.ExifVersion) === 'array') { | |||
|  | 						for (var i = 0, exifVersion = ''; i < Exif.ExifVersion.length; i++) { | |||
|  | 							exifVersion += String.fromCharCode(Exif.ExifVersion[i]); | |||
|  | 						} | |||
|  | 						Exif.ExifVersion = exifVersion; | |||
|  | 					} | |||
|  | 				} | |||
|  | 
 | |||
|  | 				return Exif; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			GPS: function() { | |||
|  | 				var GPS = null; | |||
|  | 
 | |||
|  | 				if (offsets.gpsIFD) { | |||
|  | 					try { | |||
|  | 						GPS = extractTags.call(this, offsets.gpsIFD, tags.gps); | |||
|  | 					} catch (ex) { | |||
|  | 						return null; | |||
|  | 					} | |||
|  | 
 | |||
|  | 					// iOS devices (and probably some others) do not put in GPSVersionID tag (why?..)
 | |||
|  | 					if (GPS.GPSVersionID && Basic.typeOf(GPS.GPSVersionID) === 'array') { | |||
|  | 						GPS.GPSVersionID = GPS.GPSVersionID.join('.'); | |||
|  | 					} | |||
|  | 				} | |||
|  | 
 | |||
|  | 				return GPS; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			thumb: function() { | |||
|  | 				if (offsets.IFD1) { | |||
|  | 					try { | |||
|  | 						var IFD1Tags = extractTags.call(this, offsets.IFD1, tags.thumb); | |||
|  | 						 | |||
|  | 						if ('JPEGInterchangeFormat' in IFD1Tags) { | |||
|  | 							return this.SEGMENT(offsets.tiffHeader + IFD1Tags.JPEGInterchangeFormat, IFD1Tags.JPEGInterchangeFormatLength); | |||
|  | 						} | |||
|  | 					} catch (ex) {} | |||
|  | 				} | |||
|  | 				return null; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			setExif: function(tag, value) { | |||
|  | 				// Right now only setting of width/height is possible
 | |||
|  | 				if (tag !== 'PixelXDimension' && tag !== 'PixelYDimension') { return false; } | |||
|  | 
 | |||
|  | 				return setTag.call(this, 'exif', tag, value); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			clear: function() { | |||
|  | 				__super__.clear(); | |||
|  | 				data = tags = tagDescs = Tiff = offsets = __super__ = null; | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 
 | |||
|  | 		// Check if that's APP1 and that it has EXIF
 | |||
|  | 		if (this.SHORT(0) !== 0xFFE1 || this.STRING(4, 5).toUpperCase() !== "EXIF\0") { | |||
|  | 			throw new x.ImageError(x.ImageError.INVALID_META_ERR); | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// Set read order of multi-byte data
 | |||
|  | 		this.littleEndian = (this.SHORT(idx) == 0x4949); | |||
|  | 
 | |||
|  | 		// Check if always present bytes are indeed present
 | |||
|  | 		if (this.SHORT(idx+=2) !== 0x002A) { | |||
|  | 			throw new x.ImageError(x.ImageError.INVALID_META_ERR); | |||
|  | 		} | |||
|  | 
 | |||
|  | 		offsets.IFD0 = offsets.tiffHeader + this.LONG(idx += 2); | |||
|  | 		Tiff = extractTags.call(this, offsets.IFD0, tags.tiff); | |||
|  | 
 | |||
|  | 		if ('ExifIFDPointer' in Tiff) { | |||
|  | 			offsets.exifIFD = offsets.tiffHeader + Tiff.ExifIFDPointer; | |||
|  | 			delete Tiff.ExifIFDPointer; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		if ('GPSInfoIFDPointer' in Tiff) { | |||
|  | 			offsets.gpsIFD = offsets.tiffHeader + Tiff.GPSInfoIFDPointer; | |||
|  | 			delete Tiff.GPSInfoIFDPointer; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		if (Basic.isEmptyObj(Tiff)) { | |||
|  | 			Tiff = null; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// check if we have a thumb as well
 | |||
|  | 		var IFD1Offset = this.LONG(offsets.IFD0 + this.SHORT(offsets.IFD0) * 12 + 2); | |||
|  | 		if (IFD1Offset) { | |||
|  | 			offsets.IFD1 = offsets.tiffHeader + IFD1Offset; | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function extractTags(IFD_offset, tags2extract) { | |||
|  | 			var data = this; | |||
|  | 			var length, i, tag, type, count, size, offset, value, values = [], hash = {}; | |||
|  | 			 | |||
|  | 			var types = { | |||
|  | 				1 : 'BYTE', | |||
|  | 				7 : 'UNDEFINED', | |||
|  | 				2 : 'ASCII', | |||
|  | 				3 : 'SHORT', | |||
|  | 				4 : 'LONG', | |||
|  | 				5 : 'RATIONAL', | |||
|  | 				9 : 'SLONG', | |||
|  | 				10: 'SRATIONAL' | |||
|  | 			}; | |||
|  | 
 | |||
|  | 			var sizes = { | |||
|  | 				'BYTE' 		: 1, | |||
|  | 				'UNDEFINED'	: 1, | |||
|  | 				'ASCII'		: 1, | |||
|  | 				'SHORT'		: 2, | |||
|  | 				'LONG' 		: 4, | |||
|  | 				'RATIONAL' 	: 8, | |||
|  | 				'SLONG'		: 4, | |||
|  | 				'SRATIONAL'	: 8 | |||
|  | 			}; | |||
|  | 
 | |||
|  | 			length = data.SHORT(IFD_offset); | |||
|  | 
 | |||
|  | 			// The size of APP1 including all these elements shall not exceed the 64 Kbytes specified in the JPEG standard.
 | |||
|  | 
 | |||
|  | 			for (i = 0; i < length; i++) { | |||
|  | 				values = []; | |||
|  | 
 | |||
|  | 				// Set binary reader pointer to beginning of the next tag
 | |||
|  | 				offset = IFD_offset + 2 + i*12; | |||
|  | 
 | |||
|  | 				tag = tags2extract[data.SHORT(offset)]; | |||
|  | 
 | |||
|  | 				if (tag === undefined) { | |||
|  | 					continue; // Not the tag we requested
 | |||
|  | 				} | |||
|  | 
 | |||
|  | 				type = types[data.SHORT(offset+=2)]; | |||
|  | 				count = data.LONG(offset+=2); | |||
|  | 				size = sizes[type]; | |||
|  | 
 | |||
|  | 				if (!size) { | |||
|  | 					throw new x.ImageError(x.ImageError.INVALID_META_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				offset += 4; | |||
|  | 
 | |||
|  | 				// tag can only fit 4 bytes of data, if data is larger we should look outside
 | |||
|  | 				if (size * count > 4) { | |||
|  | 					// instead of data tag contains an offset of the data
 | |||
|  | 					offset = data.LONG(offset) + offsets.tiffHeader; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// in case we left the boundaries of data throw an early exception
 | |||
|  | 				if (offset + size * count >= this.length()) { | |||
|  | 					throw new x.ImageError(x.ImageError.INVALID_META_ERR); | |||
|  | 				}  | |||
|  | 
 | |||
|  | 				// special care for the string
 | |||
|  | 				if (type === 'ASCII') { | |||
|  | 					hash[tag] = Basic.trim(data.STRING(offset, count).replace(/\0$/, '')); // strip trailing NULL
 | |||
|  | 					continue; | |||
|  | 				} else { | |||
|  | 					values = data.asArray(type, offset, count); | |||
|  | 					value = (count == 1 ? values[0] : values); | |||
|  | 
 | |||
|  | 					if (tagDescs.hasOwnProperty(tag) && typeof value != 'object') { | |||
|  | 						hash[tag] = tagDescs[tag][value]; | |||
|  | 					} else { | |||
|  | 						hash[tag] = value; | |||
|  | 					} | |||
|  | 				} | |||
|  | 			} | |||
|  | 
 | |||
|  | 			return hash; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// At the moment only setting of simple (LONG) values, that do not require offset recalculation, is supported
 | |||
|  | 		function setTag(ifd, tag, value) { | |||
|  | 			var offset, length, tagOffset, valueOffset = 0; | |||
|  | 
 | |||
|  | 			// If tag name passed translate into hex key
 | |||
|  | 			if (typeof(tag) === 'string') { | |||
|  | 				var tmpTags = tags[ifd.toLowerCase()]; | |||
|  | 				for (var hex in tmpTags) { | |||
|  | 					if (tmpTags[hex] === tag) { | |||
|  | 						tag = hex; | |||
|  | 						break; | |||
|  | 					} | |||
|  | 				} | |||
|  | 			} | |||
|  | 			offset = offsets[ifd.toLowerCase() + 'IFD']; | |||
|  | 			length = this.SHORT(offset); | |||
|  | 
 | |||
|  | 			for (var i = 0; i < length; i++) { | |||
|  | 				tagOffset = offset + 12 * i + 2; | |||
|  | 
 | |||
|  | 				if (this.SHORT(tagOffset) == tag) { | |||
|  | 					valueOffset = tagOffset + 8; | |||
|  | 					break; | |||
|  | 				} | |||
|  | 			} | |||
|  | 
 | |||
|  | 			if (!valueOffset) { | |||
|  | 				return false; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			try { | |||
|  | 				this.write(valueOffset, value, 4); | |||
|  | 			} catch(ex) { | |||
|  | 				return false; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			return true; | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	ExifParser.prototype = BinaryReader.prototype; | |||
|  | 
 | |||
|  | 	return ExifParser; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html5/image/JPEG.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * JPEG.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /** | |||
|  | @class moxie/runtime/html5/image/JPEG | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html5/image/JPEG", [ | |||
|  | 	"moxie/core/utils/Basic", | |||
|  | 	"moxie/core/Exceptions", | |||
|  | 	"moxie/runtime/html5/image/JPEGHeaders", | |||
|  | 	"moxie/runtime/html5/utils/BinaryReader", | |||
|  | 	"moxie/runtime/html5/image/ExifParser" | |||
|  | ], function(Basic, x, JPEGHeaders, BinaryReader, ExifParser) { | |||
|  | 	 | |||
|  | 	function JPEG(data) { | |||
|  | 		var _br, _hm, _ep, _info; | |||
|  | 
 | |||
|  | 		_br = new BinaryReader(data); | |||
|  | 
 | |||
|  | 		// check if it is jpeg
 | |||
|  | 		if (_br.SHORT(0) !== 0xFFD8) { | |||
|  | 			throw new x.ImageError(x.ImageError.WRONG_FORMAT); | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// backup headers
 | |||
|  | 		_hm = new JPEGHeaders(data); | |||
|  | 
 | |||
|  | 		// extract exif info
 | |||
|  | 		try { | |||
|  | 			_ep = new ExifParser(_hm.get('app1')[0]); | |||
|  | 		} catch(ex) {} | |||
|  | 
 | |||
|  | 		// get dimensions
 | |||
|  | 		_info = _getDimensions.call(this); | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			type: 'image/jpeg', | |||
|  | 
 | |||
|  | 			size: _br.length(), | |||
|  | 
 | |||
|  | 			width: _info && _info.width || 0, | |||
|  | 
 | |||
|  | 			height: _info && _info.height || 0, | |||
|  | 
 | |||
|  | 			setExif: function(tag, value) { | |||
|  | 				if (!_ep) { | |||
|  | 					return false; // or throw an exception
 | |||
|  | 				} | |||
|  | 
 | |||
|  | 				if (Basic.typeOf(tag) === 'object') { | |||
|  | 					Basic.each(tag, function(value, tag) { | |||
|  | 						_ep.setExif(tag, value); | |||
|  | 					}); | |||
|  | 				} else { | |||
|  | 					_ep.setExif(tag, value); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// update internal headers
 | |||
|  | 				_hm.set('app1', _ep.SEGMENT()); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			writeHeaders: function() { | |||
|  | 				if (!arguments.length) { | |||
|  | 					// if no arguments passed, update headers internally
 | |||
|  | 					return _hm.restore(data); | |||
|  | 				} | |||
|  | 				return _hm.restore(arguments[0]); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			stripHeaders: function(data) { | |||
|  | 				return _hm.strip(data); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			purge: function() { | |||
|  | 				_purge.call(this); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		if (_ep) { | |||
|  | 			this.meta = { | |||
|  | 				tiff: _ep.TIFF(), | |||
|  | 				exif: _ep.EXIF(), | |||
|  | 				gps: _ep.GPS(), | |||
|  | 				thumb: _getThumb() | |||
|  | 			}; | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _getDimensions(br) { | |||
|  | 			var idx = 0 | |||
|  | 			, marker | |||
|  | 			, length | |||
|  | 			; | |||
|  | 
 | |||
|  | 			if (!br) { | |||
|  | 				br = _br; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			// examine all through the end, since some images might have very large APP segments
 | |||
|  | 			while (idx <= br.length()) { | |||
|  | 				marker = br.SHORT(idx += 2); | |||
|  | 
 | |||
|  | 				if (marker >= 0xFFC0 && marker <= 0xFFC3) { // SOFn
 | |||
|  | 					idx += 5; // marker (2 bytes) + length (2 bytes) + Sample precision (1 byte)
 | |||
|  | 					return { | |||
|  | 						height: br.SHORT(idx), | |||
|  | 						width: br.SHORT(idx += 2) | |||
|  | 					}; | |||
|  | 				} | |||
|  | 				length = br.SHORT(idx += 2); | |||
|  | 				idx += length - 2; | |||
|  | 			} | |||
|  | 			return null; | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _getThumb() { | |||
|  | 			var data =  _ep.thumb() | |||
|  | 			, br | |||
|  | 			, info | |||
|  | 			; | |||
|  | 
 | |||
|  | 			if (data) { | |||
|  | 				br = new BinaryReader(data); | |||
|  | 				info = _getDimensions(br); | |||
|  | 				br.clear(); | |||
|  | 
 | |||
|  | 				if (info) { | |||
|  | 					info.data = data; | |||
|  | 					return info; | |||
|  | 				} | |||
|  | 			} | |||
|  | 			return null; | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _purge() { | |||
|  | 			if (!_ep || !_hm || !_br) {  | |||
|  | 				return; // ignore any repeating purge requests
 | |||
|  | 			} | |||
|  | 			_ep.clear(); | |||
|  | 			_hm.purge(); | |||
|  | 			_br.clear(); | |||
|  | 			_info = _hm = _ep = _br = null; | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return JPEG; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html5/image/PNG.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * PNG.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /** | |||
|  | @class moxie/runtime/html5/image/PNG | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html5/image/PNG", [ | |||
|  | 	"moxie/core/Exceptions", | |||
|  | 	"moxie/core/utils/Basic", | |||
|  | 	"moxie/runtime/html5/utils/BinaryReader" | |||
|  | ], function(x, Basic, BinaryReader) { | |||
|  | 	 | |||
|  | 	function PNG(data) { | |||
|  | 		var _br, _hm, _ep, _info; | |||
|  | 
 | |||
|  | 		_br = new BinaryReader(data); | |||
|  | 
 | |||
|  | 		// check if it's png
 | |||
|  | 		(function() { | |||
|  | 			var idx = 0, i = 0 | |||
|  | 			, signature = [0x8950, 0x4E47, 0x0D0A, 0x1A0A] | |||
|  | 			; | |||
|  | 
 | |||
|  | 			for (i = 0; i < signature.length; i++, idx += 2) { | |||
|  | 				if (signature[i] != _br.SHORT(idx)) { | |||
|  | 					throw new x.ImageError(x.ImageError.WRONG_FORMAT); | |||
|  | 				} | |||
|  | 			} | |||
|  | 		}()); | |||
|  | 
 | |||
|  | 		function _getDimensions() { | |||
|  | 			var chunk, idx; | |||
|  | 
 | |||
|  | 			chunk = _getChunkAt.call(this, 8); | |||
|  | 
 | |||
|  | 			if (chunk.type == 'IHDR') { | |||
|  | 				idx = chunk.start; | |||
|  | 				return { | |||
|  | 					width: _br.LONG(idx), | |||
|  | 					height: _br.LONG(idx += 4) | |||
|  | 				}; | |||
|  | 			} | |||
|  | 			return null; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		function _purge() { | |||
|  | 			if (!_br) { | |||
|  | 				return; // ignore any repeating purge requests
 | |||
|  | 			} | |||
|  | 			_br.clear(); | |||
|  | 			data = _info = _hm = _ep = _br = null; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		_info = _getDimensions.call(this); | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			type: 'image/png', | |||
|  | 
 | |||
|  | 			size: _br.length(), | |||
|  | 
 | |||
|  | 			width: _info.width, | |||
|  | 
 | |||
|  | 			height: _info.height, | |||
|  | 
 | |||
|  | 			purge: function() { | |||
|  | 				_purge.call(this); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		// for PNG we can safely trigger purge automatically, as we do not keep any data for later
 | |||
|  | 		_purge.call(this); | |||
|  | 
 | |||
|  | 		function _getChunkAt(idx) { | |||
|  | 			var length, type, start, CRC; | |||
|  | 
 | |||
|  | 			length = _br.LONG(idx); | |||
|  | 			type = _br.STRING(idx += 4, 4); | |||
|  | 			start = idx += 4; | |||
|  | 			CRC = _br.LONG(idx + length); | |||
|  | 
 | |||
|  | 			return { | |||
|  | 				length: length, | |||
|  | 				type: type, | |||
|  | 				start: start, | |||
|  | 				CRC: CRC | |||
|  | 			}; | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return PNG; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html5/image/ImageInfo.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * ImageInfo.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /** | |||
|  | @class moxie/runtime/html5/image/ImageInfo | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html5/image/ImageInfo", [ | |||
|  | 	"moxie/core/utils/Basic", | |||
|  | 	"moxie/core/Exceptions", | |||
|  | 	"moxie/runtime/html5/image/JPEG", | |||
|  | 	"moxie/runtime/html5/image/PNG" | |||
|  | ], function(Basic, x, JPEG, PNG) { | |||
|  | 	/** | |||
|  | 	Optional image investigation tool for HTML5 runtime. Provides the following features: | |||
|  | 	- ability to distinguish image type (JPEG or PNG) by signature | |||
|  | 	- ability to extract image width/height directly from it's internals, without preloading in memory (fast) | |||
|  | 	- ability to extract APP headers from JPEGs (Exif, GPS, etc) | |||
|  | 	- ability to replace width/height tags in extracted JPEG headers | |||
|  | 	- ability to restore APP headers, that were for example stripped during image manipulation | |||
|  | 
 | |||
|  | 	@class ImageInfo | |||
|  | 	@constructor | |||
|  | 	@param {String} data Image source as binary string | |||
|  | 	*/ | |||
|  | 	return function(data) { | |||
|  | 		var _cs = [JPEG, PNG], _img; | |||
|  | 
 | |||
|  | 		// figure out the format, throw: ImageError.WRONG_FORMAT if not supported
 | |||
|  | 		_img = (function() { | |||
|  | 			for (var i = 0; i < _cs.length; i++) { | |||
|  | 				try { | |||
|  | 					return new _cs[i](data); | |||
|  | 				} catch (ex) { | |||
|  | 					// console.info(ex);
 | |||
|  | 				} | |||
|  | 			} | |||
|  | 			throw new x.ImageError(x.ImageError.WRONG_FORMAT); | |||
|  | 		}()); | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			/** | |||
|  | 			Image Mime Type extracted from it's depths | |||
|  | 
 | |||
|  | 			@property type | |||
|  | 			@type {String} | |||
|  | 			@default '' | |||
|  | 			*/ | |||
|  | 			type: '', | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Image size in bytes | |||
|  | 
 | |||
|  | 			@property size | |||
|  | 			@type {Number} | |||
|  | 			@default 0 | |||
|  | 			*/ | |||
|  | 			size: 0, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Image width extracted from image source | |||
|  | 
 | |||
|  | 			@property width | |||
|  | 			@type {Number} | |||
|  | 			@default 0 | |||
|  | 			*/ | |||
|  | 			width: 0, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Image height extracted from image source | |||
|  | 
 | |||
|  | 			@property height | |||
|  | 			@type {Number} | |||
|  | 			@default 0 | |||
|  | 			*/ | |||
|  | 			height: 0, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Sets Exif tag. Currently applicable only for width and height tags. Obviously works only with JPEGs. | |||
|  | 
 | |||
|  | 			@method setExif | |||
|  | 			@param {String} tag Tag to set | |||
|  | 			@param {Mixed} value Value to assign to the tag | |||
|  | 			*/ | |||
|  | 			setExif: function() {}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Restores headers to the source. | |||
|  | 
 | |||
|  | 			@method writeHeaders | |||
|  | 			@param {String} data Image source as binary string | |||
|  | 			@return {String} Updated binary string | |||
|  | 			*/ | |||
|  | 			writeHeaders: function(data) { | |||
|  | 				return data; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Strip all headers from the source. | |||
|  | 
 | |||
|  | 			@method stripHeaders | |||
|  | 			@param {String} data Image source as binary string | |||
|  | 			@return {String} Updated binary string | |||
|  | 			*/ | |||
|  | 			stripHeaders: function(data) { | |||
|  | 				return data; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			Dispose resources. | |||
|  | 
 | |||
|  | 			@method purge | |||
|  | 			*/ | |||
|  | 			purge: function() { | |||
|  | 				data = null; | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		Basic.extend(this, _img); | |||
|  | 
 | |||
|  | 		this.purge = function() { | |||
|  | 			_img.purge(); | |||
|  | 			_img = null; | |||
|  | 		}; | |||
|  | 	}; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html5/image/MegaPixel.js
 | |||
|  | 
 | |||
|  | /** | |||
|  | (The MIT License) | |||
|  | 
 | |||
|  | Copyright (c) 2012 Shinichi Tomita <shinichi.tomita@gmail.com>; | |||
|  | 
 | |||
|  | Permission is hereby granted, free of charge, to any person obtaining | |||
|  | a copy of this software and associated documentation files (the | |||
|  | 'Software'), to deal in the Software without restriction, including | |||
|  | without limitation the rights to use, copy, modify, merge, publish, | |||
|  | distribute, sublicense, and/or sell copies of the Software, and to | |||
|  | permit persons to whom the Software is furnished to do so, subject to | |||
|  | the following conditions: | |||
|  | 
 | |||
|  | The above copyright notice and this permission notice shall be | |||
|  | included in all copies or substantial portions of the Software. | |||
|  | 
 | |||
|  | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, | |||
|  | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||
|  | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |||
|  | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |||
|  | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |||
|  | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |||
|  | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||
|  | */ | |||
|  | 
 | |||
|  | /** | |||
|  |  * Mega pixel image rendering library for iOS6 Safari | |||
|  |  * | |||
|  |  * Fixes iOS6 Safari's image file rendering issue for large size image (over mega-pixel), | |||
|  |  * which causes unexpected subsampling when drawing it in canvas. | |||
|  |  * By using this library, you can safely render the image with proper stretching. | |||
|  |  * | |||
|  |  * Copyright (c) 2012 Shinichi Tomita <shinichi.tomita@gmail.com> | |||
|  |  * Released under the MIT license | |||
|  |  */ | |||
|  | 
 | |||
|  | /** | |||
|  | @class moxie/runtime/html5/image/MegaPixel | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html5/image/MegaPixel", [], function() { | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	 * Rendering image element (with resizing) into the canvas element | |||
|  | 	 */ | |||
|  | 	function renderImageToCanvas(img, canvas, options) { | |||
|  | 		var iw = img.naturalWidth, ih = img.naturalHeight; | |||
|  | 		var width = options.width, height = options.height; | |||
|  | 		var x = options.x || 0, y = options.y || 0; | |||
|  | 		var ctx = canvas.getContext('2d'); | |||
|  | 		if (detectSubsampling(img)) { | |||
|  | 			iw /= 2; | |||
|  | 			ih /= 2; | |||
|  | 		} | |||
|  | 		var d = 1024; // size of tiling canvas
 | |||
|  | 		var tmpCanvas = document.createElement('canvas'); | |||
|  | 		tmpCanvas.width = tmpCanvas.height = d; | |||
|  | 		var tmpCtx = tmpCanvas.getContext('2d'); | |||
|  | 		var vertSquashRatio = detectVerticalSquash(img, iw, ih); | |||
|  | 		var sy = 0; | |||
|  | 		while (sy < ih) { | |||
|  | 			var sh = sy + d > ih ? ih - sy : d; | |||
|  | 			var sx = 0; | |||
|  | 			while (sx < iw) { | |||
|  | 				var sw = sx + d > iw ? iw - sx : d; | |||
|  | 				tmpCtx.clearRect(0, 0, d, d); | |||
|  | 				tmpCtx.drawImage(img, -sx, -sy); | |||
|  | 				var dx = (sx * width / iw + x) << 0; | |||
|  | 				var dw = Math.ceil(sw * width / iw); | |||
|  | 				var dy = (sy * height / ih / vertSquashRatio + y) << 0; | |||
|  | 				var dh = Math.ceil(sh * height / ih / vertSquashRatio); | |||
|  | 				ctx.drawImage(tmpCanvas, 0, 0, sw, sh, dx, dy, dw, dh); | |||
|  | 				sx += d; | |||
|  | 			} | |||
|  | 			sy += d; | |||
|  | 		} | |||
|  | 		tmpCanvas = tmpCtx = null; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	 * Detect subsampling in loaded image. | |||
|  | 	 * In iOS, larger images than 2M pixels may be subsampled in rendering. | |||
|  | 	 */ | |||
|  | 	function detectSubsampling(img) { | |||
|  | 		var iw = img.naturalWidth, ih = img.naturalHeight; | |||
|  | 		if (iw * ih > 1024 * 1024) { // subsampling may happen over megapixel image
 | |||
|  | 			var canvas = document.createElement('canvas'); | |||
|  | 			canvas.width = canvas.height = 1; | |||
|  | 			var ctx = canvas.getContext('2d'); | |||
|  | 			ctx.drawImage(img, -iw + 1, 0); | |||
|  | 			// subsampled image becomes half smaller in rendering size.
 | |||
|  | 			// check alpha channel value to confirm image is covering edge pixel or not.
 | |||
|  | 			// if alpha value is 0 image is not covering, hence subsampled.
 | |||
|  | 			return ctx.getImageData(0, 0, 1, 1).data[3] === 0; | |||
|  | 		} else { | |||
|  | 			return false; | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 
 | |||
|  | 	/** | |||
|  | 	 * Detecting vertical squash in loaded image. | |||
|  | 	 * Fixes a bug which squash image vertically while drawing into canvas for some images. | |||
|  | 	 */ | |||
|  | 	function detectVerticalSquash(img, iw, ih) { | |||
|  | 		var canvas = document.createElement('canvas'); | |||
|  | 		canvas.width = 1; | |||
|  | 		canvas.height = ih; | |||
|  | 		var ctx = canvas.getContext('2d'); | |||
|  | 		ctx.drawImage(img, 0, 0); | |||
|  | 		var data = ctx.getImageData(0, 0, 1, ih).data; | |||
|  | 		// search image edge pixel position in case it is squashed vertically.
 | |||
|  | 		var sy = 0; | |||
|  | 		var ey = ih; | |||
|  | 		var py = ih; | |||
|  | 		while (py > sy) { | |||
|  | 			var alpha = data[(py - 1) * 4 + 3]; | |||
|  | 			if (alpha === 0) { | |||
|  | 				ey = py; | |||
|  | 			} else { | |||
|  | 			sy = py; | |||
|  | 			} | |||
|  | 			py = (ey + sy) >> 1; | |||
|  | 		} | |||
|  | 		canvas = null; | |||
|  | 		var ratio = (py / ih); | |||
|  | 		return (ratio === 0) ? 1 : ratio; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return { | |||
|  | 		isSubsampled: detectSubsampling, | |||
|  | 		renderTo: renderImageToCanvas | |||
|  | 	}; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html5/image/Image.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * Image.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /** | |||
|  | @class moxie/runtime/html5/image/Image | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html5/image/Image", [ | |||
|  | 	"moxie/runtime/html5/Runtime", | |||
|  | 	"moxie/core/utils/Basic", | |||
|  | 	"moxie/core/Exceptions", | |||
|  | 	"moxie/core/utils/Encode", | |||
|  | 	"moxie/file/Blob", | |||
|  | 	"moxie/file/File", | |||
|  | 	"moxie/runtime/html5/image/ImageInfo", | |||
|  | 	"moxie/runtime/html5/image/MegaPixel", | |||
|  | 	"moxie/core/utils/Mime", | |||
|  | 	"moxie/core/utils/Env" | |||
|  | ], function(extensions, Basic, x, Encode, Blob, File, ImageInfo, MegaPixel, Mime, Env) { | |||
|  | 	 | |||
|  | 	function HTML5Image() { | |||
|  | 		var me = this | |||
|  | 		, _img, _imgInfo, _canvas, _binStr, _blob | |||
|  | 		, _modified = false // is set true whenever image is modified
 | |||
|  | 		, _preserveHeaders = true | |||
|  | 		; | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			loadFromBlob: function(blob) { | |||
|  | 				var comp = this, I = comp.getRuntime() | |||
|  | 				, asBinary = arguments.length > 1 ? arguments[1] : true | |||
|  | 				; | |||
|  | 
 | |||
|  | 				if (!I.can('access_binary')) { | |||
|  | 					throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				_blob = blob; | |||
|  | 
 | |||
|  | 				if (blob.isDetached()) { | |||
|  | 					_binStr = blob.getSource(); | |||
|  | 					_preload.call(this, _binStr); | |||
|  | 					return; | |||
|  | 				} else { | |||
|  | 					_readAsDataUrl.call(this, blob.getSource(), function(dataUrl) { | |||
|  | 						if (asBinary) { | |||
|  | 							_binStr = _toBinary(dataUrl); | |||
|  | 						} | |||
|  | 						_preload.call(comp, dataUrl); | |||
|  | 					}); | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			loadFromImage: function(img, exact) { | |||
|  | 				this.meta = img.meta; | |||
|  | 
 | |||
|  | 				_blob = new File(null, { | |||
|  | 					name: img.name, | |||
|  | 					size: img.size, | |||
|  | 					type: img.type | |||
|  | 				}); | |||
|  | 
 | |||
|  | 				_preload.call(this, exact ? (_binStr = img.getAsBinaryString()) : img.getAsDataURL()); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			getInfo: function() { | |||
|  | 				var I = this.getRuntime(), info; | |||
|  | 
 | |||
|  | 				if (!_imgInfo && _binStr && I.can('access_image_binary')) { | |||
|  | 					_imgInfo = new ImageInfo(_binStr); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				info = { | |||
|  | 					width: _getImg().width || 0, | |||
|  | 					height: _getImg().height || 0, | |||
|  | 					type: _blob.type || Mime.getFileMime(_blob.name), | |||
|  | 					size: _binStr && _binStr.length || _blob.size || 0, | |||
|  | 					name: _blob.name || '', | |||
|  | 					meta: _imgInfo && _imgInfo.meta || this.meta || {} | |||
|  | 				}; | |||
|  | 
 | |||
|  | 				// store thumbnail data as blob
 | |||
|  | 				if (info.meta && info.meta.thumb && !(info.meta.thumb.data instanceof Blob)) { | |||
|  | 					info.meta.thumb.data = new Blob(null, { | |||
|  | 						type: 'image/jpeg', | |||
|  | 						data: info.meta.thumb.data | |||
|  | 					}); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				return info; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			downsize: function() { | |||
|  | 				_downsize.apply(this, arguments); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			getAsCanvas: function() { | |||
|  | 				if (_canvas) { | |||
|  | 					_canvas.id = this.uid + '_canvas'; | |||
|  | 				} | |||
|  | 				return _canvas; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			getAsBlob: function(type, quality) { | |||
|  | 				if (type !== this.type) { | |||
|  | 					// if different mime type requested prepare image for conversion
 | |||
|  | 					_downsize.call(this, this.width, this.height, false); | |||
|  | 				} | |||
|  | 				return new File(null, { | |||
|  | 					name: _blob.name || '', | |||
|  | 					type: type, | |||
|  | 					data: me.getAsBinaryString.call(this, type, quality) | |||
|  | 				}); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			getAsDataURL: function(type) { | |||
|  | 				var quality = arguments[1] || 90; | |||
|  | 
 | |||
|  | 				// if image has not been modified, return the source right away
 | |||
|  | 				if (!_modified) { | |||
|  | 					return _img.src; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				if ('image/jpeg' !== type) { | |||
|  | 					return _canvas.toDataURL('image/png'); | |||
|  | 				} else { | |||
|  | 					try { | |||
|  | 						// older Geckos used to result in an exception on quality argument
 | |||
|  | 						return _canvas.toDataURL('image/jpeg', quality/100); | |||
|  | 					} catch (ex) { | |||
|  | 						return _canvas.toDataURL('image/jpeg'); | |||
|  | 					} | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			getAsBinaryString: function(type, quality) { | |||
|  | 				// if image has not been modified, return the source right away
 | |||
|  | 				if (!_modified) { | |||
|  | 					// if image was not loaded from binary string
 | |||
|  | 					if (!_binStr) { | |||
|  | 						_binStr = _toBinary(me.getAsDataURL(type, quality)); | |||
|  | 					} | |||
|  | 					return _binStr; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				if ('image/jpeg' !== type) { | |||
|  | 					_binStr = _toBinary(me.getAsDataURL(type, quality)); | |||
|  | 				} else { | |||
|  | 					var dataUrl; | |||
|  | 
 | |||
|  | 					// if jpeg
 | |||
|  | 					if (!quality) { | |||
|  | 						quality = 90; | |||
|  | 					} | |||
|  | 
 | |||
|  | 					try { | |||
|  | 						// older Geckos used to result in an exception on quality argument
 | |||
|  | 						dataUrl = _canvas.toDataURL('image/jpeg', quality/100); | |||
|  | 					} catch (ex) { | |||
|  | 						dataUrl = _canvas.toDataURL('image/jpeg'); | |||
|  | 					} | |||
|  | 
 | |||
|  | 					_binStr = _toBinary(dataUrl); | |||
|  | 
 | |||
|  | 					if (_imgInfo) { | |||
|  | 						_binStr = _imgInfo.stripHeaders(_binStr); | |||
|  | 
 | |||
|  | 						if (_preserveHeaders) { | |||
|  | 							// update dimensions info in exif
 | |||
|  | 							if (_imgInfo.meta && _imgInfo.meta.exif) { | |||
|  | 								_imgInfo.setExif({ | |||
|  | 									PixelXDimension: this.width, | |||
|  | 									PixelYDimension: this.height | |||
|  | 								}); | |||
|  | 							} | |||
|  | 
 | |||
|  | 							// re-inject the headers
 | |||
|  | 							_binStr = _imgInfo.writeHeaders(_binStr); | |||
|  | 						} | |||
|  | 
 | |||
|  | 						// will be re-created from fresh on next getInfo call
 | |||
|  | 						_imgInfo.purge(); | |||
|  | 						_imgInfo = null; | |||
|  | 					} | |||
|  | 				} | |||
|  | 
 | |||
|  | 				_modified = false; | |||
|  | 
 | |||
|  | 				return _binStr; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			destroy: function() { | |||
|  | 				me = null; | |||
|  | 				_purge.call(this); | |||
|  | 				this.getRuntime().getShim().removeInstance(this.uid); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _getImg() { | |||
|  | 			if (!_canvas && !_img) { | |||
|  | 				throw new x.ImageError(x.DOMException.INVALID_STATE_ERR); | |||
|  | 			} | |||
|  | 			return _canvas || _img; | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _toBinary(str) { | |||
|  | 			return Encode.atob(str.substring(str.indexOf('base64,') + 7)); | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _toDataUrl(str, type) { | |||
|  | 			return 'data:' + (type || '') + ';base64,' + Encode.btoa(str); | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _preload(str) { | |||
|  | 			var comp = this; | |||
|  | 
 | |||
|  | 			_img = new Image(); | |||
|  | 			_img.onerror = function() { | |||
|  | 				_purge.call(this); | |||
|  | 				comp.trigger('error', x.ImageError.WRONG_FORMAT); | |||
|  | 			}; | |||
|  | 			_img.onload = function() { | |||
|  | 				comp.trigger('load'); | |||
|  | 			}; | |||
|  | 
 | |||
|  | 			_img.src = str.substr(0, 5) == 'data:' ? str : _toDataUrl(str, _blob.type); | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _readAsDataUrl(file, callback) { | |||
|  | 			var comp = this, fr; | |||
|  | 
 | |||
|  | 			// use FileReader if it's available
 | |||
|  | 			if (window.FileReader) { | |||
|  | 				fr = new FileReader(); | |||
|  | 				fr.onload = function() { | |||
|  | 					callback(this.result); | |||
|  | 				}; | |||
|  | 				fr.onerror = function() { | |||
|  | 					comp.trigger('error', x.ImageError.WRONG_FORMAT); | |||
|  | 				}; | |||
|  | 				fr.readAsDataURL(file); | |||
|  | 			} else { | |||
|  | 				return callback(file.getAsDataURL()); | |||
|  | 			} | |||
|  | 		} | |||
|  | 
 | |||
|  | 		function _downsize(width, height, crop, preserveHeaders) { | |||
|  | 			var self = this | |||
|  | 			, scale | |||
|  | 			, mathFn | |||
|  | 			, x = 0 | |||
|  | 			, y = 0 | |||
|  | 			, img | |||
|  | 			, destWidth | |||
|  | 			, destHeight | |||
|  | 			, orientation | |||
|  | 			; | |||
|  | 
 | |||
|  | 			_preserveHeaders = preserveHeaders; // we will need to check this on export (see getAsBinaryString())
 | |||
|  | 
 | |||
|  | 			// take into account orientation tag
 | |||
|  | 			orientation = (this.meta && this.meta.tiff && this.meta.tiff.Orientation) || 1; | |||
|  | 
 | |||
|  | 			if (Basic.inArray(orientation, [5,6,7,8]) !== -1) { // values that require 90 degree rotation
 | |||
|  | 				// swap dimensions
 | |||
|  | 				var tmp = width; | |||
|  | 				width = height; | |||
|  | 				height = tmp; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			img = _getImg(); | |||
|  | 
 | |||
|  | 			// unify dimensions
 | |||
|  | 			if (!crop) { | |||
|  | 				scale = Math.min(width/img.width, height/img.height); | |||
|  | 			} else { | |||
|  | 				// one of the dimensions may exceed the actual image dimensions - we need to take the smallest value
 | |||
|  | 				width = Math.min(width, img.width); | |||
|  | 				height = Math.min(height, img.height); | |||
|  | 
 | |||
|  | 				scale = Math.max(width/img.width, height/img.height); | |||
|  | 			} | |||
|  | 		 | |||
|  | 			// we only downsize here
 | |||
|  | 			if (scale > 1 && !crop && preserveHeaders) { | |||
|  | 				this.trigger('Resize'); | |||
|  | 				return; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			// prepare canvas if necessary
 | |||
|  | 			if (!_canvas) { | |||
|  | 				_canvas = document.createElement("canvas"); | |||
|  | 			} | |||
|  | 
 | |||
|  | 			// calculate dimensions of proportionally resized image
 | |||
|  | 			destWidth = Math.round(img.width * scale);	 | |||
|  | 			destHeight = Math.round(img.height * scale); | |||
|  | 
 | |||
|  | 			// scale image and canvas
 | |||
|  | 			if (crop) { | |||
|  | 				_canvas.width = width; | |||
|  | 				_canvas.height = height; | |||
|  | 
 | |||
|  | 				// if dimensions of the resulting image still larger than canvas, center it
 | |||
|  | 				if (destWidth > width) { | |||
|  | 					x = Math.round((destWidth - width) / 2); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				if (destHeight > height) { | |||
|  | 					y = Math.round((destHeight - height) / 2); | |||
|  | 				} | |||
|  | 			} else { | |||
|  | 				_canvas.width = destWidth; | |||
|  | 				_canvas.height = destHeight; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			// rotate if required, according to orientation tag
 | |||
|  | 			if (!_preserveHeaders) { | |||
|  | 				_rotateToOrientaion(_canvas.width, _canvas.height, orientation); | |||
|  | 			} | |||
|  | 
 | |||
|  | 			_drawToCanvas.call(this, img, _canvas, -x, -y, destWidth, destHeight); | |||
|  | 
 | |||
|  | 			this.width = _canvas.width; | |||
|  | 			this.height = _canvas.height; | |||
|  | 
 | |||
|  | 			_modified = true; | |||
|  | 			self.trigger('Resize'); | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _drawToCanvas(img, canvas, x, y, w, h) { | |||
|  | 			if (Env.OS === 'iOS') {  | |||
|  | 				// avoid squish bug in iOS6
 | |||
|  | 				MegaPixel.renderTo(img, canvas, { width: w, height: h, x: x, y: y }); | |||
|  | 			} else { | |||
|  | 				var ctx = canvas.getContext('2d'); | |||
|  | 				ctx.drawImage(img, x, y, w, h); | |||
|  | 			} | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		/** | |||
|  | 		* Transform canvas coordination according to specified frame size and orientation | |||
|  | 		* Orientation value is from EXIF tag | |||
|  | 		* @author Shinichi Tomita <shinichi.tomita@gmail.com> | |||
|  | 		*/ | |||
|  | 		function _rotateToOrientaion(width, height, orientation) { | |||
|  | 			switch (orientation) { | |||
|  | 				case 5: | |||
|  | 				case 6: | |||
|  | 				case 7: | |||
|  | 				case 8: | |||
|  | 					_canvas.width = height; | |||
|  | 					_canvas.height = width; | |||
|  | 					break; | |||
|  | 				default: | |||
|  | 					_canvas.width = width; | |||
|  | 					_canvas.height = height; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			/** | |||
|  | 			1 = The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side. | |||
|  | 			2 = The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side. | |||
|  | 			3 = The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side. | |||
|  | 			4 = The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side. | |||
|  | 			5 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual top. | |||
|  | 			6 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual top. | |||
|  | 			7 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom. | |||
|  | 			8 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom. | |||
|  | 			*/ | |||
|  | 
 | |||
|  | 			var ctx = _canvas.getContext('2d'); | |||
|  | 			switch (orientation) { | |||
|  | 				case 2: | |||
|  | 					// horizontal flip
 | |||
|  | 					ctx.translate(width, 0); | |||
|  | 					ctx.scale(-1, 1); | |||
|  | 					break; | |||
|  | 				case 3: | |||
|  | 					// 180 rotate left
 | |||
|  | 					ctx.translate(width, height); | |||
|  | 					ctx.rotate(Math.PI); | |||
|  | 					break; | |||
|  | 				case 4: | |||
|  | 					// vertical flip
 | |||
|  | 					ctx.translate(0, height); | |||
|  | 					ctx.scale(1, -1); | |||
|  | 					break; | |||
|  | 				case 5: | |||
|  | 					// vertical flip + 90 rotate right
 | |||
|  | 					ctx.rotate(0.5 * Math.PI); | |||
|  | 					ctx.scale(1, -1); | |||
|  | 					break; | |||
|  | 				case 6: | |||
|  | 					// 90 rotate right
 | |||
|  | 					ctx.rotate(0.5 * Math.PI); | |||
|  | 					ctx.translate(0, -height); | |||
|  | 					break; | |||
|  | 				case 7: | |||
|  | 					// horizontal flip + 90 rotate right
 | |||
|  | 					ctx.rotate(0.5 * Math.PI); | |||
|  | 					ctx.translate(width, -height); | |||
|  | 					ctx.scale(-1, 1); | |||
|  | 					break; | |||
|  | 				case 8: | |||
|  | 					// 90 rotate left
 | |||
|  | 					ctx.rotate(-0.5 * Math.PI); | |||
|  | 					ctx.translate(-width, 0); | |||
|  | 					break; | |||
|  | 			} | |||
|  | 		} | |||
|  | 
 | |||
|  | 
 | |||
|  | 		function _purge() { | |||
|  | 			if (_imgInfo) { | |||
|  | 				_imgInfo.purge(); | |||
|  | 				_imgInfo = null; | |||
|  | 			} | |||
|  | 			_binStr = _img = _canvas = _blob = null; | |||
|  | 			_modified = false; | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return (extensions.Image = HTML5Image); | |||
|  | }); | |||
|  | 
 | |||
|  | /** | |||
|  |  * Stub for moxie/runtime/flash/Runtime | |||
|  |  * @private | |||
|  |  */ | |||
|  | define("moxie/runtime/flash/Runtime", [ | |||
|  | ], function() { | |||
|  | 	return {}; | |||
|  | }); | |||
|  | 
 | |||
|  | /** | |||
|  |  * Stub for moxie/runtime/silverlight/Runtime | |||
|  |  * @private | |||
|  |  */ | |||
|  | define("moxie/runtime/silverlight/Runtime", [ | |||
|  | ], function() { | |||
|  | 	return {}; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html4/Runtime.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * Runtime.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /*global File:true */ | |||
|  | 
 | |||
|  | /** | |||
|  | Defines constructor for HTML4 runtime. | |||
|  | 
 | |||
|  | @class moxie/runtime/html4/Runtime | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html4/Runtime", [ | |||
|  | 	"moxie/core/utils/Basic", | |||
|  | 	"moxie/core/Exceptions", | |||
|  | 	"moxie/runtime/Runtime", | |||
|  | 	"moxie/core/utils/Env" | |||
|  | ], function(Basic, x, Runtime, Env) { | |||
|  | 	 | |||
|  | 	var type = 'html4', extensions = {}; | |||
|  | 
 | |||
|  | 	function Html4Runtime(options) { | |||
|  | 		var I = this | |||
|  | 		, Test = Runtime.capTest | |||
|  | 		, True = Runtime.capTrue | |||
|  | 		; | |||
|  | 
 | |||
|  | 		Runtime.call(this, options, type, { | |||
|  | 			access_binary: Test(window.FileReader || window.File && File.getAsDataURL), | |||
|  | 			access_image_binary: false, | |||
|  | 			display_media: Test(extensions.Image && (Env.can('create_canvas') || Env.can('use_data_uri_over32kb'))), | |||
|  | 			do_cors: false, | |||
|  | 			drag_and_drop: false, | |||
|  | 			filter_by_extension: Test(function() { // if you know how to feature-detect this, please suggest
 | |||
|  | 				return (Env.browser === 'Chrome' && Env.verComp(Env.version, 28, '>=')) ||  | |||
|  | 					(Env.browser === 'IE' && Env.verComp(Env.version, 10, '>=')) ||  | |||
|  | 					(Env.browser === 'Safari' && Env.verComp(Env.version, 7, '>=')); | |||
|  | 			}()), | |||
|  | 			resize_image: function() { | |||
|  | 				return extensions.Image && I.can('access_binary') && Env.can('create_canvas'); | |||
|  | 			}, | |||
|  | 			report_upload_progress: false, | |||
|  | 			return_response_headers: false, | |||
|  | 			return_response_type: function(responseType) { | |||
|  | 				if (responseType === 'json' && !!window.JSON) { | |||
|  | 					return true; | |||
|  | 				}  | |||
|  | 				return !!~Basic.inArray(responseType, ['text', 'document', '']); | |||
|  | 			}, | |||
|  | 			return_status_code: function(code) { | |||
|  | 				return !Basic.arrayDiff(code, [200, 404]); | |||
|  | 			}, | |||
|  | 			select_file: function() { | |||
|  | 				return Env.can('use_fileinput'); | |||
|  | 			}, | |||
|  | 			select_multiple: false, | |||
|  | 			send_binary_string: false, | |||
|  | 			send_custom_headers: false, | |||
|  | 			send_multipart: true, | |||
|  | 			slice_blob: false, | |||
|  | 			stream_upload: function() { | |||
|  | 				return I.can('select_file'); | |||
|  | 			}, | |||
|  | 			summon_file_dialog: function() { // yeah... some dirty sniffing here...
 | |||
|  | 				return I.can('select_file') && ( | |||
|  | 					(Env.browser === 'Firefox' && Env.verComp(Env.version, 4, '>=')) || | |||
|  | 					(Env.browser === 'Opera' && Env.verComp(Env.version, 12, '>=')) || | |||
|  | 					(Env.browser === 'IE' && Env.verComp(Env.version, 10, '>=')) || | |||
|  | 					!!~Basic.inArray(Env.browser, ['Chrome', 'Safari']) | |||
|  | 				); | |||
|  | 			}, | |||
|  | 			upload_filesize: True, | |||
|  | 			use_http_method: function(methods) { | |||
|  | 				return !Basic.arrayDiff(methods, ['GET', 'POST']); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 
 | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			init : function() { | |||
|  | 				this.trigger("Init"); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			destroy: (function(destroy) { // extend default destroy method
 | |||
|  | 				return function() { | |||
|  | 					destroy.call(I); | |||
|  | 					destroy = I = null; | |||
|  | 				}; | |||
|  | 			}(this.destroy)) | |||
|  | 		}); | |||
|  | 
 | |||
|  | 		Basic.extend(this.getShim(), extensions); | |||
|  | 	} | |||
|  | 
 | |||
|  | 	Runtime.addConstructor(type, Html4Runtime); | |||
|  | 
 | |||
|  | 	return extensions; | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html4/file/FileInput.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * FileInput.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /** | |||
|  | @class moxie/runtime/html4/file/FileInput | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html4/file/FileInput", [ | |||
|  | 	"moxie/runtime/html4/Runtime", | |||
|  | 	"moxie/file/File", | |||
|  | 	"moxie/core/utils/Basic", | |||
|  | 	"moxie/core/utils/Dom", | |||
|  | 	"moxie/core/utils/Events", | |||
|  | 	"moxie/core/utils/Mime", | |||
|  | 	"moxie/core/utils/Env" | |||
|  | ], function(extensions, File, Basic, Dom, Events, Mime, Env) { | |||
|  | 	 | |||
|  | 	function FileInput() { | |||
|  | 		var _uid, _mimes = [], _options; | |||
|  | 
 | |||
|  | 		function addInput() { | |||
|  | 			var comp = this, I = comp.getRuntime(), shimContainer, browseButton, currForm, form, input, uid; | |||
|  | 
 | |||
|  | 			uid = Basic.guid('uid_'); | |||
|  | 
 | |||
|  | 			shimContainer = I.getShimContainer(); // we get new ref everytime to avoid memory leaks in IE
 | |||
|  | 
 | |||
|  | 			if (_uid) { // move previous form out of the view
 | |||
|  | 				currForm = Dom.get(_uid + '_form'); | |||
|  | 				if (currForm) { | |||
|  | 					Basic.extend(currForm.style, { top: '100%' }); | |||
|  | 				} | |||
|  | 			} | |||
|  | 
 | |||
|  | 			// build form in DOM, since innerHTML version not able to submit file for some reason
 | |||
|  | 			form = document.createElement('form'); | |||
|  | 			form.setAttribute('id', uid + '_form'); | |||
|  | 			form.setAttribute('method', 'post'); | |||
|  | 			form.setAttribute('enctype', 'multipart/form-data'); | |||
|  | 			form.setAttribute('encoding', 'multipart/form-data'); | |||
|  | 
 | |||
|  | 			Basic.extend(form.style, { | |||
|  | 				overflow: 'hidden', | |||
|  | 				position: 'absolute', | |||
|  | 				top: 0, | |||
|  | 				left: 0, | |||
|  | 				width: '100%', | |||
|  | 				height: '100%' | |||
|  | 			}); | |||
|  | 
 | |||
|  | 			input = document.createElement('input'); | |||
|  | 			input.setAttribute('id', uid); | |||
|  | 			input.setAttribute('type', 'file'); | |||
|  | 			input.setAttribute('name', _options.name || 'Filedata'); | |||
|  | 			input.setAttribute('accept', _mimes.join(',')); | |||
|  | 
 | |||
|  | 			Basic.extend(input.style, { | |||
|  | 				fontSize: '999px', | |||
|  | 				opacity: 0 | |||
|  | 			}); | |||
|  | 
 | |||
|  | 			form.appendChild(input); | |||
|  | 			shimContainer.appendChild(form); | |||
|  | 
 | |||
|  | 			// prepare file input to be placed underneath the browse_button element
 | |||
|  | 			Basic.extend(input.style, { | |||
|  | 				position: 'absolute', | |||
|  | 				top: 0, | |||
|  | 				left: 0, | |||
|  | 				width: '100%', | |||
|  | 				height: '100%' | |||
|  | 			}); | |||
|  | 
 | |||
|  | 			if (Env.browser === 'IE' && Env.verComp(Env.version, 10, '<')) { | |||
|  | 				Basic.extend(input.style, { | |||
|  | 					filter : "progid:DXImageTransform.Microsoft.Alpha(opacity=0)" | |||
|  | 				}); | |||
|  | 			} | |||
|  | 
 | |||
|  | 			input.onchange = function() { // there should be only one handler for this
 | |||
|  | 				var file; | |||
|  | 
 | |||
|  | 				if (!this.value) { | |||
|  | 					return; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				if (this.files) { // check if browser is fresh enough
 | |||
|  | 					file = this.files[0]; | |||
|  | 
 | |||
|  | 					// ignore empty files (IE10 for example hangs if you try to send them via XHR)
 | |||
|  | 					if (file.size === 0) { | |||
|  | 						form.parentNode.removeChild(form); | |||
|  | 						return; | |||
|  | 					} | |||
|  | 				} else { | |||
|  | 					file = { | |||
|  | 						name: this.value | |||
|  | 					}; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				file = new File(I.uid, file); | |||
|  | 
 | |||
|  | 				// clear event handler
 | |||
|  | 				this.onchange = function() {};  | |||
|  | 				addInput.call(comp);  | |||
|  | 
 | |||
|  | 				comp.files = [file]; | |||
|  | 
 | |||
|  | 				// substitute all ids with file uids (consider file.uid read-only - we cannot do it the other way around)
 | |||
|  | 				input.setAttribute('id', file.uid); | |||
|  | 				form.setAttribute('id', file.uid + '_form'); | |||
|  | 				 | |||
|  | 				comp.trigger('change'); | |||
|  | 
 | |||
|  | 				input = form = null; | |||
|  | 			}; | |||
|  | 
 | |||
|  | 
 | |||
|  | 			// route click event to the input
 | |||
|  | 			if (I.can('summon_file_dialog')) { | |||
|  | 				browseButton = Dom.get(_options.browse_button); | |||
|  | 				Events.removeEvent(browseButton, 'click', comp.uid); | |||
|  | 				Events.addEvent(browseButton, 'click', function(e) { | |||
|  | 					if (input && !input.disabled) { // for some reason FF (up to 8.0.1 so far) lets to click disabled input[type=file]
 | |||
|  | 						input.click(); | |||
|  | 					} | |||
|  | 					e.preventDefault(); | |||
|  | 				}, comp.uid); | |||
|  | 			} | |||
|  | 
 | |||
|  | 			_uid = uid; | |||
|  | 
 | |||
|  | 			shimContainer = currForm = browseButton = null; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			init: function(options) { | |||
|  | 				var comp = this, I = comp.getRuntime(), shimContainer; | |||
|  | 
 | |||
|  | 				// figure out accept string
 | |||
|  | 				_options = options; | |||
|  | 				_mimes = options.accept.mimes || Mime.extList2mimes(options.accept, I.can('filter_by_extension')); | |||
|  | 
 | |||
|  | 				shimContainer = I.getShimContainer(); | |||
|  | 
 | |||
|  | 				(function() { | |||
|  | 					var browseButton, zIndex, top; | |||
|  | 
 | |||
|  | 					browseButton = Dom.get(options.browse_button); | |||
|  | 
 | |||
|  | 					// Route click event to the input[type=file] element for browsers that support such behavior
 | |||
|  | 					if (I.can('summon_file_dialog')) { | |||
|  | 						if (Dom.getStyle(browseButton, 'position') === 'static') { | |||
|  | 							browseButton.style.position = 'relative'; | |||
|  | 						} | |||
|  | 
 | |||
|  | 						zIndex = parseInt(Dom.getStyle(browseButton, 'z-index'), 10) || 1; | |||
|  | 
 | |||
|  | 						browseButton.style.zIndex = zIndex; | |||
|  | 						shimContainer.style.zIndex = zIndex - 1; | |||
|  | 					} | |||
|  | 
 | |||
|  | 					/* Since we have to place input[type=file] on top of the browse_button for some browsers, | |||
|  | 					browse_button loses interactivity, so we restore it here */ | |||
|  | 					top = I.can('summon_file_dialog') ? browseButton : shimContainer; | |||
|  | 
 | |||
|  | 					Events.addEvent(top, 'mouseover', function() { | |||
|  | 						comp.trigger('mouseenter'); | |||
|  | 					}, comp.uid); | |||
|  | 
 | |||
|  | 					Events.addEvent(top, 'mouseout', function() { | |||
|  | 						comp.trigger('mouseleave'); | |||
|  | 					}, comp.uid); | |||
|  | 
 | |||
|  | 					Events.addEvent(top, 'mousedown', function() { | |||
|  | 						comp.trigger('mousedown'); | |||
|  | 					}, comp.uid); | |||
|  | 
 | |||
|  | 					Events.addEvent(Dom.get(options.container), 'mouseup', function() { | |||
|  | 						comp.trigger('mouseup'); | |||
|  | 					}, comp.uid); | |||
|  | 
 | |||
|  | 					browseButton = null; | |||
|  | 				}()); | |||
|  | 
 | |||
|  | 				addInput.call(this); | |||
|  | 
 | |||
|  | 				shimContainer = null; | |||
|  | 
 | |||
|  | 				// trigger ready event asynchronously
 | |||
|  | 				comp.trigger({ | |||
|  | 					type: 'ready', | |||
|  | 					async: true | |||
|  | 				}); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 
 | |||
|  | 			disable: function(state) { | |||
|  | 				var input; | |||
|  | 
 | |||
|  | 				if ((input = Dom.get(_uid))) { | |||
|  | 					input.disabled = !!state; | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			destroy: function() { | |||
|  | 				var I = this.getRuntime() | |||
|  | 				, shim = I.getShim() | |||
|  | 				, shimContainer = I.getShimContainer() | |||
|  | 				; | |||
|  | 				 | |||
|  | 				Events.removeAllEvents(shimContainer, this.uid); | |||
|  | 				Events.removeAllEvents(_options && Dom.get(_options.container), this.uid); | |||
|  | 				Events.removeAllEvents(_options && Dom.get(_options.browse_button), this.uid); | |||
|  | 				 | |||
|  | 				if (shimContainer) { | |||
|  | 					shimContainer.innerHTML = ''; | |||
|  | 				} | |||
|  | 
 | |||
|  | 				shim.removeInstance(this.uid); | |||
|  | 
 | |||
|  | 				_uid = _mimes = _options = shimContainer = shim = null; | |||
|  | 			} | |||
|  | 		}); | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return (extensions.FileInput = FileInput); | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html4/file/FileReader.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * FileReader.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /** | |||
|  | @class moxie/runtime/html4/file/FileReader | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html4/file/FileReader", [ | |||
|  | 	"moxie/runtime/html4/Runtime", | |||
|  | 	"moxie/runtime/html5/file/FileReader" | |||
|  | ], function(extensions, FileReader) { | |||
|  | 	return (extensions.FileReader = FileReader); | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html4/xhr/XMLHttpRequest.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * XMLHttpRequest.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /** | |||
|  | @class moxie/runtime/html4/xhr/XMLHttpRequest | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html4/xhr/XMLHttpRequest", [ | |||
|  | 	"moxie/runtime/html4/Runtime", | |||
|  | 	"moxie/core/utils/Basic", | |||
|  | 	"moxie/core/utils/Dom", | |||
|  | 	"moxie/core/utils/Url", | |||
|  | 	"moxie/core/Exceptions", | |||
|  | 	"moxie/core/utils/Events", | |||
|  | 	"moxie/file/Blob", | |||
|  | 	"moxie/xhr/FormData" | |||
|  | ], function(extensions, Basic, Dom, Url, x, Events, Blob, FormData) { | |||
|  | 	 | |||
|  | 	function XMLHttpRequest() { | |||
|  | 		var _status, _response, _iframe; | |||
|  | 
 | |||
|  | 		function cleanup(cb) { | |||
|  | 			var target = this, uid, form, inputs, i, hasFile = false; | |||
|  | 
 | |||
|  | 			if (!_iframe) { | |||
|  | 				return; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			uid = _iframe.id.replace(/_iframe$/, ''); | |||
|  | 
 | |||
|  | 			form = Dom.get(uid + '_form'); | |||
|  | 			if (form) { | |||
|  | 				inputs = form.getElementsByTagName('input'); | |||
|  | 				i = inputs.length; | |||
|  | 
 | |||
|  | 				while (i--) { | |||
|  | 					switch (inputs[i].getAttribute('type')) { | |||
|  | 						case 'hidden': | |||
|  | 							inputs[i].parentNode.removeChild(inputs[i]); | |||
|  | 							break; | |||
|  | 						case 'file': | |||
|  | 							hasFile = true; // flag the case for later
 | |||
|  | 							break; | |||
|  | 					} | |||
|  | 				} | |||
|  | 				inputs = []; | |||
|  | 
 | |||
|  | 				if (!hasFile) { // we need to keep the form for sake of possible retries
 | |||
|  | 					form.parentNode.removeChild(form); | |||
|  | 				} | |||
|  | 				form = null; | |||
|  | 			} | |||
|  | 
 | |||
|  | 			// without timeout, request is marked as canceled (in console)
 | |||
|  | 			setTimeout(function() { | |||
|  | 				Events.removeEvent(_iframe, 'load', target.uid); | |||
|  | 				if (_iframe.parentNode) { // #382
 | |||
|  | 					_iframe.parentNode.removeChild(_iframe); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// check if shim container has any other children, if - not, remove it as well
 | |||
|  | 				var shimContainer = target.getRuntime().getShimContainer(); | |||
|  | 				if (!shimContainer.children.length) { | |||
|  | 					shimContainer.parentNode.removeChild(shimContainer); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				shimContainer = _iframe = null; | |||
|  | 				cb(); | |||
|  | 			}, 1); | |||
|  | 		} | |||
|  | 
 | |||
|  | 		Basic.extend(this, { | |||
|  | 			send: function(meta, data) { | |||
|  | 				var target = this, I = target.getRuntime(), uid, form, input, blob; | |||
|  | 
 | |||
|  | 				_status = _response = null; | |||
|  | 
 | |||
|  | 				function createIframe() { | |||
|  | 					var container = I.getShimContainer() || document.body | |||
|  | 					, temp = document.createElement('div') | |||
|  | 					; | |||
|  | 
 | |||
|  | 					// IE 6 won't be able to set the name using setAttribute or iframe.name
 | |||
|  | 					temp.innerHTML = '<iframe id="' + uid + '_iframe" name="' + uid + '_iframe" src="javascript:""" style="display:none"></iframe>'; | |||
|  | 					_iframe = temp.firstChild; | |||
|  | 					container.appendChild(_iframe); | |||
|  | 
 | |||
|  | 					/* _iframe.onreadystatechange = function() { | |||
|  | 						console.info(_iframe.readyState); | |||
|  | 					};*/ | |||
|  | 
 | |||
|  | 					Events.addEvent(_iframe, 'load', function() { // _iframe.onload doesn't work in IE lte 8
 | |||
|  | 						var el; | |||
|  | 
 | |||
|  | 						try { | |||
|  | 							el = _iframe.contentWindow.document || _iframe.contentDocument || window.frames[_iframe.id].document; | |||
|  | 
 | |||
|  | 							// try to detect some standard error pages
 | |||
|  | 							if (/^4(0[0-9]|1[0-7]|2[2346])\s/.test(el.title)) { // test if title starts with 4xx HTTP error
 | |||
|  | 								_status = el.title.replace(/^(\d+).*$/, '$1'); | |||
|  | 							} else { | |||
|  | 								_status = 200; | |||
|  | 								// get result
 | |||
|  | 								_response = Basic.trim(el.body.innerHTML); | |||
|  | 
 | |||
|  | 								// we need to fire these at least once
 | |||
|  | 								target.trigger({ | |||
|  | 									type: 'progress', | |||
|  | 									loaded: _response.length, | |||
|  | 									total: _response.length | |||
|  | 								}); | |||
|  | 
 | |||
|  | 								if (blob) { // if we were uploading a file
 | |||
|  | 									target.trigger({ | |||
|  | 										type: 'uploadprogress', | |||
|  | 										loaded: blob.size || 1025, | |||
|  | 										total: blob.size || 1025 | |||
|  | 									}); | |||
|  | 								} | |||
|  | 							} | |||
|  | 						} catch (ex) { | |||
|  | 							if (Url.hasSameOrigin(meta.url)) { | |||
|  | 								// if response is sent with error code, iframe in IE gets redirected to res://ieframe.dll/http_x.htm
 | |||
|  | 								// which obviously results to cross domain error (wtf?)
 | |||
|  | 								_status = 404; | |||
|  | 							} else { | |||
|  | 								cleanup.call(target, function() { | |||
|  | 									target.trigger('error'); | |||
|  | 								}); | |||
|  | 								return; | |||
|  | 							} | |||
|  | 						}	 | |||
|  | 					 | |||
|  | 						cleanup.call(target, function() { | |||
|  | 							target.trigger('load'); | |||
|  | 						}); | |||
|  | 					}, target.uid); | |||
|  | 				} // end createIframe
 | |||
|  | 
 | |||
|  | 				// prepare data to be sent and convert if required
 | |||
|  | 				if (data instanceof FormData && data.hasBlob()) { | |||
|  | 					blob = data.getBlob(); | |||
|  | 					uid = blob.uid; | |||
|  | 					input = Dom.get(uid); | |||
|  | 					form = Dom.get(uid + '_form'); | |||
|  | 					if (!form) { | |||
|  | 						throw new x.DOMException(x.DOMException.NOT_FOUND_ERR); | |||
|  | 					} | |||
|  | 				} else { | |||
|  | 					uid = Basic.guid('uid_'); | |||
|  | 
 | |||
|  | 					form = document.createElement('form'); | |||
|  | 					form.setAttribute('id', uid + '_form'); | |||
|  | 					form.setAttribute('method', meta.method); | |||
|  | 					form.setAttribute('enctype', 'multipart/form-data'); | |||
|  | 					form.setAttribute('encoding', 'multipart/form-data'); | |||
|  | 
 | |||
|  | 					I.getShimContainer().appendChild(form); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// set upload target
 | |||
|  | 				form.setAttribute('target', uid + '_iframe'); | |||
|  | 
 | |||
|  | 				if (data instanceof FormData) { | |||
|  | 					data.each(function(value, name) { | |||
|  | 						if (value instanceof Blob) { | |||
|  | 							if (input) { | |||
|  | 								input.setAttribute('name', name); | |||
|  | 							} | |||
|  | 						} else { | |||
|  | 							var hidden = document.createElement('input'); | |||
|  | 
 | |||
|  | 							Basic.extend(hidden, { | |||
|  | 								type : 'hidden', | |||
|  | 								name : name, | |||
|  | 								value : value | |||
|  | 							}); | |||
|  | 
 | |||
|  | 							// make sure that input[type="file"], if it's there, comes last
 | |||
|  | 							if (input) { | |||
|  | 								form.insertBefore(hidden, input); | |||
|  | 							} else { | |||
|  | 								form.appendChild(hidden); | |||
|  | 							} | |||
|  | 						} | |||
|  | 					}); | |||
|  | 				} | |||
|  | 
 | |||
|  | 				// set destination url
 | |||
|  | 				form.setAttribute("action", meta.url); | |||
|  | 
 | |||
|  | 				createIframe(); | |||
|  | 				form.submit(); | |||
|  | 				target.trigger('loadstart'); | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			getStatus: function() { | |||
|  | 				return _status; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			getResponse: function(responseType) { | |||
|  | 				if ('json' === responseType) { | |||
|  | 					// strip off <pre>..</pre> tags that might be enclosing the response
 | |||
|  | 					if (Basic.typeOf(_response) === 'string' && !!window.JSON) { | |||
|  | 						try { | |||
|  | 							return JSON.parse(_response.replace(/^\s*<pre[^>]*>/, '').replace(/<\/pre>\s*$/, '')); | |||
|  | 						} catch (ex) { | |||
|  | 							return null; | |||
|  | 						} | |||
|  | 					}  | |||
|  | 				} else if ('document' === responseType) { | |||
|  | 
 | |||
|  | 				} | |||
|  | 				return _response; | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			abort: function() { | |||
|  | 				var target = this; | |||
|  | 
 | |||
|  | 				if (_iframe && _iframe.contentWindow) { | |||
|  | 					if (_iframe.contentWindow.stop) { // FireFox/Safari/Chrome
 | |||
|  | 						_iframe.contentWindow.stop(); | |||
|  | 					} else if (_iframe.contentWindow.document.execCommand) { // IE
 | |||
|  | 						_iframe.contentWindow.document.execCommand('Stop'); | |||
|  | 					} else { | |||
|  | 						_iframe.src = "about:blank"; | |||
|  | 					} | |||
|  | 				} | |||
|  | 
 | |||
|  | 				cleanup.call(this, function() { | |||
|  | 					// target.dispatchEvent('readystatechange');
 | |||
|  | 					target.dispatchEvent('abort'); | |||
|  | 				}); | |||
|  | 			} | |||
|  | 		}); | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return (extensions.XMLHttpRequest = XMLHttpRequest); | |||
|  | }); | |||
|  | 
 | |||
|  | // Included from: src/javascript/runtime/html4/image/Image.js
 | |||
|  | 
 | |||
|  | /** | |||
|  |  * Image.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /** | |||
|  | @class moxie/runtime/html4/image/Image | |||
|  | @private | |||
|  | */ | |||
|  | define("moxie/runtime/html4/image/Image", [ | |||
|  | 	"moxie/runtime/html4/Runtime", | |||
|  | 	"moxie/runtime/html5/image/Image" | |||
|  | ], function(extensions, Image) { | |||
|  | 	return (extensions.Image = Image); | |||
|  | }); | |||
|  | 
 | |||
|  | expose(["moxie/core/utils/Basic","moxie/core/utils/Env","moxie/core/I18n","moxie/core/utils/Mime","moxie/core/utils/Dom","moxie/core/Exceptions","moxie/core/EventTarget","moxie/runtime/Runtime","moxie/runtime/RuntimeClient","moxie/file/FileInput","moxie/core/utils/Encode","moxie/file/Blob","moxie/file/File","moxie/file/FileDrop","moxie/file/FileReader","moxie/core/utils/Url","moxie/runtime/RuntimeTarget","moxie/file/FileReaderSync","moxie/xhr/FormData","moxie/xhr/XMLHttpRequest","moxie/runtime/Transporter","moxie/image/Image","moxie/core/utils/Events"]); | |||
|  | })(this); | |||
|  | /** | |||
|  |  * o.js | |||
|  |  * | |||
|  |  * Copyright 2013, Moxiecode Systems AB | |||
|  |  * Released under GPL License. | |||
|  |  * | |||
|  |  * License: http://www.plupload.com/license
 | |||
|  |  * Contributing: http://www.plupload.com/contributing
 | |||
|  |  */ | |||
|  | 
 | |||
|  | /*global moxie:true */ | |||
|  | 
 | |||
|  | /** | |||
|  | Globally exposed namespace with the most frequently used public classes and handy methods. | |||
|  | 
 | |||
|  | @class o | |||
|  | @static | |||
|  | @private | |||
|  | */ | |||
|  | (function(exports) { | |||
|  | 	"use strict"; | |||
|  | 
 | |||
|  | 	var o = {}, inArray = exports.moxie.core.utils.Basic.inArray; | |||
|  | 
 | |||
|  | 	// directly add some public classes
 | |||
|  | 	// (we do it dynamically here, since for custom builds we cannot know beforehand what modules were included)
 | |||
|  | 	(function addAlias(ns) { | |||
|  | 		var name, itemType; | |||
|  | 		for (name in ns) { | |||
|  | 			itemType = typeof(ns[name]); | |||
|  | 			if (itemType === 'object' && !~inArray(name, ['Exceptions', 'Env', 'Mime'])) { | |||
|  | 				addAlias(ns[name]); | |||
|  | 			} else if (itemType === 'function') { | |||
|  | 				o[name] = ns[name]; | |||
|  | 			} | |||
|  | 		} | |||
|  | 	})(exports.moxie); | |||
|  | 
 | |||
|  | 	// add some manually
 | |||
|  | 	o.Env = exports.moxie.core.utils.Env; | |||
|  | 	o.Mime = exports.moxie.core.utils.Mime; | |||
|  | 	o.Exceptions = exports.moxie.core.Exceptions; | |||
|  | 
 | |||
|  | 	// expose globally
 | |||
|  | 	exports.mOxie = o; | |||
|  | 	if (!exports.o) { | |||
|  | 		exports.o = o; | |||
|  | 	} | |||
|  | 	return o; | |||
|  | })(this); |