summaryrefslogtreecommitdiffstats
path: root/wp-includes/js/plupload/moxie.js
diff options
context:
space:
mode:
Diffstat (limited to 'wp-includes/js/plupload/moxie.js')
-rw-r--r--wp-includes/js/plupload/moxie.js9901
1 files changed, 9901 insertions, 0 deletions
diff --git a/wp-includes/js/plupload/moxie.js b/wp-includes/js/plupload/moxie.js
new file mode 100644
index 0000000..c7d26f4
--- /dev/null
+++ b/wp-includes/js/plupload/moxie.js
@@ -0,0 +1,9901 @@
+;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:&quot;&quot;" 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);