Passed
Branch development (e0e718)
by Nils
04:45
created

includes/libraries/Plupload/moxie.js   F

Complexity

Total Complexity 1641
Complexity/F 2.42

Size

Lines of Code 11714
Function Count 679

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 0
nc 0
dl 0
loc 11714
rs 2.4
c 1
b 0
f 0
wmc 1641
mnd 9
bc 1433
fnc 679
bpm 2.1104
cpm 2.4167
noi 144

How to fix   Complexity   

Complexity

Complex classes like includes/libraries/Plupload/moxie.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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