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