Make WordPress Core

Ticket #48193: trac-48193-wp-loop-with-tests.diff

File trac-48193-wp-loop-with-tests.diff, 356.2 KB (added by wpscholar, 6 years ago)
  • src/wp-includes/query.php

    From a3fa06f74a481f093b4dbf51fd572a9bb82d0577 Mon Sep 17 00:00:00 2001
    From: Micah Wood <micah@wpscholar.com>
    Date: Tue, 1 Oct 2019 14:34:33 -0400
    Subject: [PATCH] Initial implementation of wp_loop() function.
    
    ---
     src/wp-includes/query.php | 47 +++++++++++++++++++++++++++++++++++++++
     1 file changed, 47 insertions(+)
    
    diff --git a/src/wp-includes/query.php b/src/wp-includes/query.php
    index 75c93d6aa1..065e55ca01 100644
    a b function generate_postdata( $post ) { 
    11611161
    11621162        return false;
    11631163}
     1164
     1165/**
     1166 * Simplifies the WordPress loop.
     1167 *
     1168 * @param WP_Query|WP_Post[] $iterable
     1169 *
     1170 * @return Generator
     1171 */
     1172function wp_loop( $iterable = null ) {
     1173
     1174        if ( null === $iterable ) {
     1175                $iterable = $GLOBALS['wp_query'];
     1176        }
     1177
     1178        $posts = $iterable;
     1179
     1180        if ( is_object( $iterable ) && property_exists( $iterable, 'posts' ) ) {
     1181                $posts = $iterable->posts;
     1182        }
     1183
     1184        if ( ! is_array( $posts ) ) {
     1185                /* translators: Data type */
     1186                _doing_it_wrong( __FUNCTION__, sprintf( __( 'Expected an array, received %s instead.' ), gettype( $posts ) ), '3.1.0' );
     1187
     1188                return false;
     1189        }
     1190
     1191        global $post;
     1192
     1193        // Save the global post object so we can restore it later
     1194        $save_post = $post;
     1195
     1196        try {
     1197                foreach ( $posts as $post ) {
     1198
     1199                        setup_postdata( $post );
     1200                        yield $post;
     1201
     1202                }
     1203        } finally {
     1204
     1205                wp_reset_postdata();
     1206                // Restore the global post object
     1207                $post = $save_post;
     1208
     1209        }
     1210}
  • src/wp-includes/query.php

    -- 
    2.20.1
    
    From 89993bfd6331d6808847d44806ccd591843e3b1a Mon Sep 17 00:00:00 2001
    From: Micah Wood <micah@wpscholar.com>
    Date: Tue, 1 Oct 2019 14:37:24 -0400
    Subject: [PATCH] Updated docblock
    
    ---
     src/wp-includes/query.php | 6 +++++-
     1 file changed, 5 insertions(+), 1 deletion(-)
    
    diff --git a/src/wp-includes/query.php b/src/wp-includes/query.php
    index 065e55ca01..b2b6809e29 100644
    a b function generate_postdata( $post ) { 
    11651165/**
    11661166 * Simplifies the WordPress loop.
    11671167 *
    1168  * @param WP_Query|WP_Post[] $iterable
     1168 * @since 5.x
     1169 *
     1170 * @global WP_Query $wp_query WordPress Query object.
     1171 *
     1172 * @param WP_Query|WP_Post[] $iterable WordPress query object or an array of posts.
    11691173 *
    11701174 * @return Generator
    11711175 */
  • Gruntfile.js

    -- 
    2.20.1
    
    From 9d88dbac5851069d4827574319910010964a9b0e Mon Sep 17 00:00:00 2001
    From: Jonathan Desrosiers <desrosj@git.wordpress.org>
    Date: Sun, 3 Nov 2019 17:07:42 +0000
    Subject: [PATCH] External Libraries: Revert [46634-46635].
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    On further investigation, Plupload changed it’s license to a non-GPL compatible license. The newest, GPL compatible version is being used already in Core.
    
    Unprops desrosj.
    See #48277.
    
    git-svn-id: https://develop.svn.wordpress.org/trunk@46638 602fd350-edb4-49c9-b593-d223f7449a82
    ---
     Gruntfile.js                                  |   27 -
     package-lock.json                             |    5 -
     package.json                                  |    1 -
     .../{wp => vendor}/plupload/handlers.js       |   22 +-
     src/js/_enqueues/vendor/plupload/license.txt  |  339 +
     src/js/_enqueues/vendor/plupload/moxie.js     | 9901 +++++++++++++++++
     src/js/_enqueues/vendor/plupload/plupload.js  | 2379 ++++
     .../{wp => vendor}/plupload/wp-plupload.js    |   16 +-
     src/wp-includes/script-loader.php             |    6 +-
     9 files changed, 12640 insertions(+), 56 deletions(-)
     rename src/js/_enqueues/{wp => vendor}/plupload/handlers.js (97%)
     create mode 100644 src/js/_enqueues/vendor/plupload/license.txt
     create mode 100644 src/js/_enqueues/vendor/plupload/moxie.js
     create mode 100644 src/js/_enqueues/vendor/plupload/plupload.js
     rename src/js/_enqueues/{wp => vendor}/plupload/wp-plupload.js (98%)
    
    diff --git a/Gruntfile.js b/Gruntfile.js
    index 5022288bb7..b2905cb3f3 100644
    a b module.exports = function(grunt) { 
    167167                                                [ WORKING_DIR + 'wp-includes/js/jquery/jquery-migrate.min.js' ]: [ './node_modules/jquery-migrate/dist/jquery-migrate.min.js' ],
    168168                                                [ WORKING_DIR + 'wp-includes/js/jquery/jquery.form.js' ]: [ './node_modules/jquery-form/src/jquery.form.js' ],
    169169                                                [ WORKING_DIR + 'wp-includes/js/masonry.min.js' ]: [ './node_modules/masonry-layout/dist/masonry.pkgd.min.js' ],
    170                                                 [ WORKING_DIR + 'wp-includes/js/plupload/moxie.js' ]: [ './node_modules/plupload/js/moxie.js' ],
    171                                                 [ WORKING_DIR + 'wp-includes/js/plupload/plupload.js' ]: [ './node_modules/plupload/js/plupload.dev.js' ],
    172170                                                [ WORKING_DIR + 'wp-includes/js/twemoji.js' ]: [ './node_modules/twemoji/dist/twemoji.js' ],
    173171                                                [ WORKING_DIR + 'wp-includes/js/underscore.js' ]: [ './node_modules/underscore/underscore.js' ],
    174172                                        },
    module.exports = function(grunt) { 
    296294                                        [ WORKING_DIR + 'wp-includes/js/heartbeat.js' ]: [ './src/js/_enqueues/wp/heartbeat.js' ],
    297295                                        [ WORKING_DIR + 'wp-includes/js/mce-view.js' ]: [ './src/js/_enqueues/wp/mce-view.js' ],
    298296                                        [ WORKING_DIR + 'wp-includes/js/media-editor.js' ]: [ './src/js/_enqueues/wp/media/editor.js' ],
    299                                         [ WORKING_DIR + 'wp-includes/js/plupload/handlers.js' ]: [ './src/js/_enqueues/wp/plupload/handlers.js' ],
    300                                         [ WORKING_DIR + 'wp-includes/js/plupload/wp-plupload.js' ]: [ './src/js/_enqueues/wp/plupload/wp-plupload.js' ],
    301297                                        [ WORKING_DIR + 'wp-includes/js/quicktags.js' ]: [ './src/js/_enqueues/lib/quicktags.js' ],
    302298                                        [ WORKING_DIR + 'wp-includes/js/shortcode.js' ]: [ './src/js/_enqueues/wp/shortcode.js' ],
    303299                                        [ WORKING_DIR + 'wp-includes/js/utils.js' ]: [ './src/js/_enqueues/lib/cookies.js' ],
    module.exports = function(grunt) { 
    10791075                                                dest: SOURCE_DIR + 'wp-includes/'
    10801076                                        }
    10811077                                ]
    1082                         },
    1083                         plupload: {
    1084                                 options: {
    1085                                         patterns: [
    1086                                                 {
    1087                                                         match: /;var MXI_DEBUG = true;/g,
    1088                                                         replacement: function () {
    1089                                                                 return ';var MXI_DEBUG = false;';
    1090                                                         }
    1091                                                 }
    1092                                         ]
    1093                                 },
    1094                                 files: [
    1095                                         {
    1096                                                 expand: true,
    1097                                                 flatten: true,
    1098                                                 src: [
    1099                                                         'build/wp-includes/js/plupload/moxie.js'
    1100                                                 ],
    1101                                                 dest: 'build/wp-includes/js/plupload/'
    1102                                         }
    1103                                 ]
    11041078                        }
    11051079                },
    11061080                _watch: {
    module.exports = function(grunt) { 
    13781352                'webpack:prod',
    13791353                'webpack:dev',
    13801354                'copy:js',
    1381                 'replace:plupload',
    13821355                'file_append',
    13831356                'uglify:all',
    13841357                'concat:tinymce',
  • package-lock.json

    diff --git a/package-lock.json b/package-lock.json
    index 6793572618..f410170868 100644
    a b  
    1578415784                                }
    1578515785                        }
    1578615786                },
    15787                 "plupload": {
    15788                         "version": "2.3.6",
    15789                         "resolved": "https://registry.npmjs.org/plupload/-/plupload-2.3.6.tgz",
    15790                         "integrity": "sha1-Jgis9zXV6NuDJIck142BdXu0OGY="
    15791                 },
    1579215787                "plur": {
    1579315788                        "version": "3.1.1",
    1579415789                        "resolved": "https://registry.npmjs.org/plur/-/plur-3.1.1.tgz",
  • package.json

    diff --git a/package.json b/package.json
    index 941a831906..8a474e0172 100644
    a b  
    125125                "lodash": "4.17.15",
    126126                "masonry-layout": "3.3.2",
    127127                "moment": "2.22.2",
    128                 "plupload": "2.3.6",
    129128                "polyfill-library": "3.27.4",
    130129                "react": "16.9.0",
    131130                "react-dom": "16.9.0",
  • plupload/handlers.js

    diff --git a/src/js/_enqueues/wp/plupload/handlers.js b/src/js/_enqueues/vendor/plupload/handlers.js
    similarity index 97%
    rename from src/js/_enqueues/wp/plupload/handlers.js
    rename to src/js/_enqueues/vendor/plupload/handlers.js
    index 8c39578075..9116a64eac 100644
    old new function itemAjaxError( id, message ) { 
    216216                return;
    217217
    218218        item.html( '<div class="error-div">' +
    219                 '<a class="dismiss" href="#">' + pluploadL10n.dismiss + '</a>' +
    220                 '<strong>' + pluploadL10n.error_uploading.replace( '%s', jQuery.trim( filename )) + '</strong> ' +
    221                 message +
    222                 '</div>' ).data( 'last-err', id );
     219                                '<a class="dismiss" href="#">' + pluploadL10n.dismiss + '</a>' +
     220                                '<strong>' + pluploadL10n.error_uploading.replace( '%s', jQuery.trim( filename )) + '</strong> ' +
     221                                message +
     222                                '</div>' ).data( 'last-err', id );
    223223}
    224224
    225225function deleteSuccess( data ) {
    function uploadError( fileObj, errorCode, message, up ) { 
    323323                case plupload.SECURITY_ERROR:
    324324                        wpQueueError( pluploadL10n.security_error );
    325325                        break;
    326                 /*              case plupload.UPLOAD_ERROR.UPLOAD_STOPPED:
    327                                 case plupload.UPLOAD_ERROR.FILE_CANCELLED:
    328                                         jQuery( '#media-item-' + fileObj.id ).remove();
    329                                         break;*/
     326/*              case plupload.UPLOAD_ERROR.UPLOAD_STOPPED:
     327                case plupload.UPLOAD_ERROR.FILE_CANCELLED:
     328                        jQuery( '#media-item-' + fileObj.id ).remove();
     329                        break;*/
    330330                default:
    331331                        wpFileError( fileObj, pluploadL10n.default_error );
    332332        }
    jQuery( document ).ready( function( $ ) { 
    461461                                        action: 'media-create-image-subsizes',
    462462                                        _wpnonce: wpUploaderInit.multipart_params._wpnonce,
    463463                                        attachment_id: id,
    464                                         _wp_upload_failed_cleanup: true
     464                                        _wp_upload_failed_cleanup: true,
    465465                                }
    466466                        });
    467467
    jQuery( document ).ready( function( $ ) { 
    489489                                action: 'media-create-image-subsizes',
    490490                                _wpnonce: wpUploaderInit.multipart_params._wpnonce,
    491491                                attachment_id: id,
    492                                 _legacy_support: 'true'
     492                                _legacy_support: 'true',
    493493                        }
    494494                }).done( function( response ) {
    495495                        var message;
    jQuery( document ).ready( function( $ ) { 
    512512
    513513                        wpQueueError( pluploadL10n.http_error_image );
    514514                });
    515         };
     515        }
    516516
    517517        // init and set the uploader
    518518        uploader_init = function() {
  • new file src/js/_enqueues/vendor/plupload/license.txt

    diff --git a/src/js/_enqueues/vendor/plupload/license.txt b/src/js/_enqueues/vendor/plupload/license.txt
    new file mode 100644
    index 0000000000..d511905c16
    - +  
     1                    GNU GENERAL PUBLIC LICENSE
     2                       Version 2, June 1991
     3
     4 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
     5 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
     6 Everyone is permitted to copy and distribute verbatim copies
     7 of this license document, but changing it is not allowed.
     8
     9                            Preamble
     10
     11  The licenses for most software are designed to take away your
     12freedom to share and change it.  By contrast, the GNU General Public
     13License is intended to guarantee your freedom to share and change free
     14software--to make sure the software is free for all its users.  This
     15General Public License applies to most of the Free Software
     16Foundation's software and to any other program whose authors commit to
     17using it.  (Some other Free Software Foundation software is covered by
     18the GNU Lesser General Public License instead.)  You can apply it to
     19your programs, too.
     20
     21  When we speak of free software, we are referring to freedom, not
     22price.  Our General Public Licenses are designed to make sure that you
     23have the freedom to distribute copies of free software (and charge for
     24this service if you wish), that you receive source code or can get it
     25if you want it, that you can change the software or use pieces of it
     26in new free programs; and that you know you can do these things.
     27
     28  To protect your rights, we need to make restrictions that forbid
     29anyone to deny you these rights or to ask you to surrender the rights.
     30These restrictions translate to certain responsibilities for you if you
     31distribute copies of the software, or if you modify it.
     32
     33  For example, if you distribute copies of such a program, whether
     34gratis or for a fee, you must give the recipients all the rights that
     35you have.  You must make sure that they, too, receive or can get the
     36source code.  And you must show them these terms so they know their
     37rights.
     38
     39  We protect your rights with two steps: (1) copyright the software, and
     40(2) offer you this license which gives you legal permission to copy,
     41distribute and/or modify the software.
     42
     43  Also, for each author's protection and ours, we want to make certain
     44that everyone understands that there is no warranty for this free
     45software.  If the software is modified by someone else and passed on, we
     46want its recipients to know that what they have is not the original, so
     47that any problems introduced by others will not reflect on the original
     48authors' reputations.
     49
     50  Finally, any free program is threatened constantly by software
     51patents.  We wish to avoid the danger that redistributors of a free
     52program will individually obtain patent licenses, in effect making the
     53program proprietary.  To prevent this, we have made it clear that any
     54patent must be licensed for everyone's free use or not licensed at all.
     55
     56  The precise terms and conditions for copying, distribution and
     57modification follow.
     58
     59                    GNU GENERAL PUBLIC LICENSE
     60   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
     61
     62  0. This License applies to any program or other work which contains
     63a notice placed by the copyright holder saying it may be distributed
     64under the terms of this General Public License.  The "Program", below,
     65refers to any such program or work, and a "work based on the Program"
     66means either the Program or any derivative work under copyright law:
     67that is to say, a work containing the Program or a portion of it,
     68either verbatim or with modifications and/or translated into another
     69language.  (Hereinafter, translation is included without limitation in
     70the term "modification".)  Each licensee is addressed as "you".
     71
     72Activities other than copying, distribution and modification are not
     73covered by this License; they are outside its scope.  The act of
     74running the Program is not restricted, and the output from the Program
     75is covered only if its contents constitute a work based on the
     76Program (independent of having been made by running the Program).
     77Whether that is true depends on what the Program does.
     78
     79  1. You may copy and distribute verbatim copies of the Program's
     80source code as you receive it, in any medium, provided that you
     81conspicuously and appropriately publish on each copy an appropriate
     82copyright notice and disclaimer of warranty; keep intact all the
     83notices that refer to this License and to the absence of any warranty;
     84and give any other recipients of the Program a copy of this License
     85along with the Program.
     86
     87You may charge a fee for the physical act of transferring a copy, and
     88you may at your option offer warranty protection in exchange for a fee.
     89
     90  2. You may modify your copy or copies of the Program or any portion
     91of it, thus forming a work based on the Program, and copy and
     92distribute such modifications or work under the terms of Section 1
     93above, provided that you also meet all of these conditions:
     94
     95    a) You must cause the modified files to carry prominent notices
     96    stating that you changed the files and the date of any change.
     97
     98    b) You must cause any work that you distribute or publish, that in
     99    whole or in part contains or is derived from the Program or any
     100    part thereof, to be licensed as a whole at no charge to all third
     101    parties under the terms of this License.
     102
     103    c) If the modified program normally reads commands interactively
     104    when run, you must cause it, when started running for such
     105    interactive use in the most ordinary way, to print or display an
     106    announcement including an appropriate copyright notice and a
     107    notice that there is no warranty (or else, saying that you provide
     108    a warranty) and that users may redistribute the program under
     109    these conditions, and telling the user how to view a copy of this
     110    License.  (Exception: if the Program itself is interactive but
     111    does not normally print such an announcement, your work based on
     112    the Program is not required to print an announcement.)
     113
     114These requirements apply to the modified work as a whole.  If
     115identifiable sections of that work are not derived from the Program,
     116and can be reasonably considered independent and separate works in
     117themselves, then this License, and its terms, do not apply to those
     118sections when you distribute them as separate works.  But when you
     119distribute the same sections as part of a whole which is a work based
     120on the Program, the distribution of the whole must be on the terms of
     121this License, whose permissions for other licensees extend to the
     122entire whole, and thus to each and every part regardless of who wrote it.
     123
     124Thus, it is not the intent of this section to claim rights or contest
     125your rights to work written entirely by you; rather, the intent is to
     126exercise the right to control the distribution of derivative or
     127collective works based on the Program.
     128
     129In addition, mere aggregation of another work not based on the Program
     130with the Program (or with a work based on the Program) on a volume of
     131a storage or distribution medium does not bring the other work under
     132the scope of this License.
     133
     134  3. You may copy and distribute the Program (or a work based on it,
     135under Section 2) in object code or executable form under the terms of
     136Sections 1 and 2 above provided that you also do one of the following:
     137
     138    a) Accompany it with the complete corresponding machine-readable
     139    source code, which must be distributed under the terms of Sections
     140    1 and 2 above on a medium customarily used for software interchange; or,
     141
     142    b) Accompany it with a written offer, valid for at least three
     143    years, to give any third party, for a charge no more than your
     144    cost of physically performing source distribution, a complete
     145    machine-readable copy of the corresponding source code, to be
     146    distributed under the terms of Sections 1 and 2 above on a medium
     147    customarily used for software interchange; or,
     148
     149    c) Accompany it with the information you received as to the offer
     150    to distribute corresponding source code.  (This alternative is
     151    allowed only for noncommercial distribution and only if you
     152    received the program in object code or executable form with such
     153    an offer, in accord with Subsection b above.)
     154
     155The source code for a work means the preferred form of the work for
     156making modifications to it.  For an executable work, complete source
     157code means all the source code for all modules it contains, plus any
     158associated interface definition files, plus the scripts used to
     159control compilation and installation of the executable.  However, as a
     160special exception, the source code distributed need not include
     161anything that is normally distributed (in either source or binary
     162form) with the major components (compiler, kernel, and so on) of the
     163operating system on which the executable runs, unless that component
     164itself accompanies the executable.
     165
     166If distribution of executable or object code is made by offering
     167access to copy from a designated place, then offering equivalent
     168access to copy the source code from the same place counts as
     169distribution of the source code, even though third parties are not
     170compelled to copy the source along with the object code.
     171
     172  4. You may not copy, modify, sublicense, or distribute the Program
     173except as expressly provided under this License.  Any attempt
     174otherwise to copy, modify, sublicense or distribute the Program is
     175void, and will automatically terminate your rights under this License.
     176However, parties who have received copies, or rights, from you under
     177this License will not have their licenses terminated so long as such
     178parties remain in full compliance.
     179
     180  5. You are not required to accept this License, since you have not
     181signed it.  However, nothing else grants you permission to modify or
     182distribute the Program or its derivative works.  These actions are
     183prohibited by law if you do not accept this License.  Therefore, by
     184modifying or distributing the Program (or any work based on the
     185Program), you indicate your acceptance of this License to do so, and
     186all its terms and conditions for copying, distributing or modifying
     187the Program or works based on it.
     188
     189  6. Each time you redistribute the Program (or any work based on the
     190Program), the recipient automatically receives a license from the
     191original licensor to copy, distribute or modify the Program subject to
     192these terms and conditions.  You may not impose any further
     193restrictions on the recipients' exercise of the rights granted herein.
     194You are not responsible for enforcing compliance by third parties to
     195this License.
     196
     197  7. If, as a consequence of a court judgment or allegation of patent
     198infringement or for any other reason (not limited to patent issues),
     199conditions are imposed on you (whether by court order, agreement or
     200otherwise) that contradict the conditions of this License, they do not
     201excuse you from the conditions of this License.  If you cannot
     202distribute so as to satisfy simultaneously your obligations under this
     203License and any other pertinent obligations, then as a consequence you
     204may not distribute the Program at all.  For example, if a patent
     205license would not permit royalty-free redistribution of the Program by
     206all those who receive copies directly or indirectly through you, then
     207the only way you could satisfy both it and this License would be to
     208refrain entirely from distribution of the Program.
     209
     210If any portion of this section is held invalid or unenforceable under
     211any particular circumstance, the balance of the section is intended to
     212apply and the section as a whole is intended to apply in other
     213circumstances.
     214
     215It is not the purpose of this section to induce you to infringe any
     216patents or other property right claims or to contest validity of any
     217such claims; this section has the sole purpose of protecting the
     218integrity of the free software distribution system, which is
     219implemented by public license practices.  Many people have made
     220generous contributions to the wide range of software distributed
     221through that system in reliance on consistent application of that
     222system; it is up to the author/donor to decide if he or she is willing
     223to distribute software through any other system and a licensee cannot
     224impose that choice.
     225
     226This section is intended to make thoroughly clear what is believed to
     227be a consequence of the rest of this License.
     228
     229  8. If the distribution and/or use of the Program is restricted in
     230certain countries either by patents or by copyrighted interfaces, the
     231original copyright holder who places the Program under this License
     232may add an explicit geographical distribution limitation excluding
     233those countries, so that distribution is permitted only in or among
     234countries not thus excluded.  In such case, this License incorporates
     235the limitation as if written in the body of this License.
     236
     237  9. The Free Software Foundation may publish revised and/or new versions
     238of the General Public License from time to time.  Such new versions will
     239be similar in spirit to the present version, but may differ in detail to
     240address new problems or concerns.
     241
     242Each version is given a distinguishing version number.  If the Program
     243specifies a version number of this License which applies to it and "any
     244later version", you have the option of following the terms and conditions
     245either of that version or of any later version published by the Free
     246Software Foundation.  If the Program does not specify a version number of
     247this License, you may choose any version ever published by the Free Software
     248Foundation.
     249
     250  10. If you wish to incorporate parts of the Program into other free
     251programs whose distribution conditions are different, write to the author
     252to ask for permission.  For software which is copyrighted by the Free
     253Software Foundation, write to the Free Software Foundation; we sometimes
     254make exceptions for this.  Our decision will be guided by the two goals
     255of preserving the free status of all derivatives of our free software and
     256of promoting the sharing and reuse of software generally.
     257
     258                            NO WARRANTY
     259
     260  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
     261FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
     262OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
     263PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
     264OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     265MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
     266TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
     267PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
     268REPAIR OR CORRECTION.
     269
     270  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
     271WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
     272REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
     273INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
     274OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
     275TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
     276YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
     277PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
     278POSSIBILITY OF SUCH DAMAGES.
     279
     280                     END OF TERMS AND CONDITIONS
     281
     282            How to Apply These Terms to Your New Programs
     283
     284  If you develop a new program, and you want it to be of the greatest
     285possible use to the public, the best way to achieve this is to make it
     286free software which everyone can redistribute and change under these terms.
     287
     288  To do so, attach the following notices to the program.  It is safest
     289to attach them to the start of each source file to most effectively
     290convey the exclusion of warranty; and each file should have at least
     291the "copyright" line and a pointer to where the full notice is found.
     292
     293    <one line to give the program's name and a brief idea of what it does.>
     294    Copyright (C) <year>  <name of author>
     295
     296    This program is free software; you can redistribute it and/or modify
     297    it under the terms of the GNU General Public License as published by
     298    the Free Software Foundation; either version 2 of the License, or
     299    (at your option) any later version.
     300
     301    This program is distributed in the hope that it will be useful,
     302    but WITHOUT ANY WARRANTY; without even the implied warranty of
     303    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     304    GNU General Public License for more details.
     305
     306    You should have received a copy of the GNU General Public License along
     307    with this program; if not, write to the Free Software Foundation, Inc.,
     308    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
     309
     310Also add information on how to contact you by electronic and paper mail.
     311
     312If the program is interactive, make it output a short notice like this
     313when it starts in an interactive mode:
     314
     315    Gnomovision version 69, Copyright (C) year name of author
     316    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
     317    This is free software, and you are welcome to redistribute it
     318    under certain conditions; type `show c' for details.
     319
     320The hypothetical commands `show w' and `show c' should show the appropriate
     321parts of the General Public License.  Of course, the commands you use may
     322be called something other than `show w' and `show c'; they could even be
     323mouse-clicks or menu items--whatever suits your program.
     324
     325You should also get your employer (if you work as a programmer) or your
     326school, if any, to sign a "copyright disclaimer" for the program, if
     327necessary.  Here is a sample; alter the names:
     328
     329  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
     330  `Gnomovision' (which makes passes at compilers) written by James Hacker.
     331
     332  <signature of Ty Coon>, 1 April 1989
     333  Ty Coon, President of Vice
     334
     335This General Public License does not permit incorporating your program into
     336proprietary programs.  If your program is a subroutine library, you may
     337consider it more useful to permit linking proprietary applications with the
     338library.  If this is what you want to do, use the GNU Lesser General
     339Public License instead of this License.
  • new file src/js/_enqueues/vendor/plupload/moxie.js

    diff --git a/src/js/_enqueues/vendor/plupload/moxie.js b/src/js/_enqueues/vendor/plupload/moxie.js
    new file mode 100644
    index 0000000000..c7d26f4c4e
    - +  
     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
     113define('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
     508define("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
     1213define("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
     1278define("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
     1473define('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
     1656define('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
     1815define('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
     2119define('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
     2722define('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
     2898define('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
     3227define('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
     3407define('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
     3585define('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
     3670define('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
     3846define('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
     4121define('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
     4248define('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
     4290define('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
     4363define("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
     4482define("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
     5453define("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
     5590define("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/**
     6192Defines constructor for HTML5 runtime.
     6193
     6194@class moxie/runtime/html5/Runtime
     6195@private
     6196*/
     6197define("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
     6314define('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*/
     6491define("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*/
     6672define("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*/
     6721define("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*/
     6924define("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*/
     7006define("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*/
     7379define("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*/
     7599define("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*/
     7743define("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*/
     8232define("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*/
     8392define("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*/
     8494define("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
     8619Copyright (c) 2012 Shinichi Tomita <shinichi.tomita@gmail.com>;
     8620
     8621Permission is hereby granted, free of charge, to any person obtaining
     8622a copy of this software and associated documentation files (the
     8623'Software'), to deal in the Software without restriction, including
     8624without limitation the rights to use, copy, modify, merge, publish,
     8625distribute, sublicense, and/or sell copies of the Software, and to
     8626permit persons to whom the Software is furnished to do so, subject to
     8627the following conditions:
     8628
     8629The above copyright notice and this permission notice shall be
     8630included in all copies or substantial portions of the Software.
     8631
     8632THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
     8633EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     8634MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
     8635IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
     8636CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
     8637TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
     8638SOFTWARE 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*/
     8656define("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*/
     8767define("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 */
     9196define("moxie/runtime/flash/Runtime", [
     9197], function() {
     9198        return {};
     9199});
     9200
     9201/**
     9202 * Stub for moxie/runtime/silverlight/Runtime
     9203 * @private
     9204 */
     9205define("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/**
     9225Defines constructor for HTML4 runtime.
     9226
     9227@class moxie/runtime/html4/Runtime
     9228@private
     9229*/
     9230define("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*/
     9333define("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
     9351                        shimContainer = I.getShimContainer(); // we get new ref everytime to avoid memory leaks in IE
     9352
     9353                        if (_uid) { // move previous form out of the view
     9354                                currForm = Dom.get(_uid + '_form');
     9355                                if (currForm) {
     9356                                        Basic.extend(currForm.style, { top: '100%' });
     9357                                }
     9358                        }
     9359
     9360                        // build form in DOM, since innerHTML version not able to submit file for some reason
     9361                        form = document.createElement('form');
     9362                        form.setAttribute('id', uid + '_form');
     9363                        form.setAttribute('method', 'post');
     9364                        form.setAttribute('enctype', 'multipart/form-data');
     9365                        form.setAttribute('encoding', 'multipart/form-data');
     9366
     9367                        Basic.extend(form.style, {
     9368                                overflow: 'hidden',
     9369                                position: 'absolute',
     9370                                top: 0,
     9371                                left: 0,
     9372                                width: '100%',
     9373                                height: '100%'
     9374                        });
     9375
     9376                        input = document.createElement('input');
     9377                        input.setAttribute('id', uid);
     9378                        input.setAttribute('type', 'file');
     9379                        input.setAttribute('name', _options.name || 'Filedata');
     9380                        input.setAttribute('accept', _mimes.join(','));
     9381
     9382                        Basic.extend(input.style, {
     9383                                fontSize: '999px',
     9384                                opacity: 0
     9385                        });
     9386
     9387                        form.appendChild(input);
     9388                        shimContainer.appendChild(form);
     9389
     9390                        // prepare file input to be placed underneath the browse_button element
     9391                        Basic.extend(input.style, {
     9392                                position: 'absolute',
     9393                                top: 0,
     9394                                left: 0,
     9395                                width: '100%',
     9396                                height: '100%'
     9397                        });
     9398
     9399                        if (Env.browser === 'IE' && Env.verComp(Env.version, 10, '<')) {
     9400                                Basic.extend(input.style, {
     9401                                        filter : "progid:DXImageTransform.Microsoft.Alpha(opacity=0)"
     9402                                });
     9403                        }
     9404
     9405                        input.onchange = function() { // there should be only one handler for this
     9406                                var file;
     9407
     9408                                if (!this.value) {
     9409                                        return;
     9410                                }
     9411
     9412                                if (this.files) { // check if browser is fresh enough
     9413                                        file = this.files[0];
     9414
     9415                                        // ignore empty files (IE10 for example hangs if you try to send them via XHR)
     9416                                        if (file.size === 0) {
     9417                                                form.parentNode.removeChild(form);
     9418                                                return;
     9419                                        }
     9420                                } else {
     9421                                        file = {
     9422                                                name: this.value
     9423                                        };
     9424                                }
     9425
     9426                                file = new File(I.uid, file);
     9427
     9428                                // clear event handler
     9429                                this.onchange = function() {};
     9430                                addInput.call(comp);
     9431
     9432                                comp.files = [file];
     9433
     9434                                // substitute all ids with file uids (consider file.uid read-only - we cannot do it the other way around)
     9435                                input.setAttribute('id', file.uid);
     9436                                form.setAttribute('id', file.uid + '_form');
     9437                               
     9438                                comp.trigger('change');
     9439
     9440                                input = form = null;
     9441                        };
     9442
     9443
     9444                        // route click event to the input
     9445                        if (I.can('summon_file_dialog')) {
     9446                                browseButton = Dom.get(_options.browse_button);
     9447                                Events.removeEvent(browseButton, 'click', comp.uid);
     9448                                Events.addEvent(browseButton, 'click', function(e) {
     9449                                        if (input && !input.disabled) { // for some reason FF (up to 8.0.1 so far) lets to click disabled input[type=file]
     9450                                                input.click();
     9451                                        }
     9452                                        e.preventDefault();
     9453                                }, comp.uid);
     9454                        }
     9455
     9456                        _uid = uid;
     9457
     9458                        shimContainer = currForm = browseButton = null;
     9459                }
     9460
     9461                Basic.extend(this, {
     9462                        init: function(options) {
     9463                                var comp = this, I = comp.getRuntime(), shimContainer;
     9464
     9465                                // figure out accept string
     9466                                _options = options;
     9467                                _mimes = options.accept.mimes || Mime.extList2mimes(options.accept, I.can('filter_by_extension'));
     9468
     9469                                shimContainer = I.getShimContainer();
     9470
     9471                                (function() {
     9472                                        var browseButton, zIndex, top;
     9473
     9474                                        browseButton = Dom.get(options.browse_button);
     9475
     9476                                        // Route click event to the input[type=file] element for browsers that support such behavior
     9477                                        if (I.can('summon_file_dialog')) {
     9478                                                if (Dom.getStyle(browseButton, 'position') === 'static') {
     9479                                                        browseButton.style.position = 'relative';
     9480                                                }
     9481
     9482                                                zIndex = parseInt(Dom.getStyle(browseButton, 'z-index'), 10) || 1;
     9483
     9484                                                browseButton.style.zIndex = zIndex;
     9485                                                shimContainer.style.zIndex = zIndex - 1;
     9486                                        }
     9487
     9488                                        /* Since we have to place input[type=file] on top of the browse_button for some browsers,
     9489                                        browse_button loses interactivity, so we restore it here */
     9490                                        top = I.can('summon_file_dialog') ? browseButton : shimContainer;
     9491
     9492                                        Events.addEvent(top, 'mouseover', function() {
     9493                                                comp.trigger('mouseenter');
     9494                                        }, comp.uid);
     9495
     9496                                        Events.addEvent(top, 'mouseout', function() {
     9497                                                comp.trigger('mouseleave');
     9498                                        }, comp.uid);
     9499
     9500                                        Events.addEvent(top, 'mousedown', function() {
     9501                                                comp.trigger('mousedown');
     9502                                        }, comp.uid);
     9503
     9504                                        Events.addEvent(Dom.get(options.container), 'mouseup', function() {
     9505                                                comp.trigger('mouseup');
     9506                                        }, comp.uid);
     9507
     9508                                        browseButton = null;
     9509                                }());
     9510
     9511                                addInput.call(this);
     9512
     9513                                shimContainer = null;
     9514
     9515                                // trigger ready event asynchronously
     9516                                comp.trigger({
     9517                                        type: 'ready',
     9518                                        async: true
     9519                                });
     9520                        },
     9521
     9522
     9523                        disable: function(state) {
     9524                                var input;
     9525
     9526                                if ((input = Dom.get(_uid))) {
     9527                                        input.disabled = !!state;
     9528                                }
     9529                        },
     9530
     9531                        destroy: function() {
     9532                                var I = this.getRuntime()
     9533                                , shim = I.getShim()
     9534                                , shimContainer = I.getShimContainer()
     9535                                ;
     9536                               
     9537                                Events.removeAllEvents(shimContainer, this.uid);
     9538                                Events.removeAllEvents(_options && Dom.get(_options.container), this.uid);
     9539                                Events.removeAllEvents(_options && Dom.get(_options.browse_button), this.uid);
     9540                               
     9541                                if (shimContainer) {
     9542                                        shimContainer.innerHTML = '';
     9543                                }
     9544
     9545                                shim.removeInstance(this.uid);
     9546
     9547                                _uid = _mimes = _options = shimContainer = shim = null;
     9548                        }
     9549                });
     9550        }
     9551
     9552        return (extensions.FileInput = FileInput);
     9553});
     9554
     9555// Included from: src/javascript/runtime/html4/file/FileReader.js
     9556
     9557/**
     9558 * FileReader.js
     9559 *
     9560 * Copyright 2013, Moxiecode Systems AB
     9561 * Released under GPL License.
     9562 *
     9563 * License: http://www.plupload.com/license
     9564 * Contributing: http://www.plupload.com/contributing
     9565 */
     9566
     9567/**
     9568@class moxie/runtime/html4/file/FileReader
     9569@private
     9570*/
     9571define("moxie/runtime/html4/file/FileReader", [
     9572        "moxie/runtime/html4/Runtime",
     9573        "moxie/runtime/html5/file/FileReader"
     9574], function(extensions, FileReader) {
     9575        return (extensions.FileReader = FileReader);
     9576});
     9577
     9578// Included from: src/javascript/runtime/html4/xhr/XMLHttpRequest.js
     9579
     9580/**
     9581 * XMLHttpRequest.js
     9582 *
     9583 * Copyright 2013, Moxiecode Systems AB
     9584 * Released under GPL License.
     9585 *
     9586 * License: http://www.plupload.com/license
     9587 * Contributing: http://www.plupload.com/contributing
     9588 */
     9589
     9590/**
     9591@class moxie/runtime/html4/xhr/XMLHttpRequest
     9592@private
     9593*/
     9594define("moxie/runtime/html4/xhr/XMLHttpRequest", [
     9595        "moxie/runtime/html4/Runtime",
     9596        "moxie/core/utils/Basic",
     9597        "moxie/core/utils/Dom",
     9598        "moxie/core/utils/Url",
     9599        "moxie/core/Exceptions",
     9600        "moxie/core/utils/Events",
     9601        "moxie/file/Blob",
     9602        "moxie/xhr/FormData"
     9603], function(extensions, Basic, Dom, Url, x, Events, Blob, FormData) {
     9604       
     9605        function XMLHttpRequest() {
     9606                var _status, _response, _iframe;
     9607
     9608                function cleanup(cb) {
     9609                        var target = this, uid, form, inputs, i, hasFile = false;
     9610
     9611                        if (!_iframe) {
     9612                                return;
     9613                        }
     9614
     9615                        uid = _iframe.id.replace(/_iframe$/, '');
     9616
     9617                        form = Dom.get(uid + '_form');
     9618                        if (form) {
     9619                                inputs = form.getElementsByTagName('input');
     9620                                i = inputs.length;
     9621
     9622                                while (i--) {
     9623                                        switch (inputs[i].getAttribute('type')) {
     9624                                                case 'hidden':
     9625                                                        inputs[i].parentNode.removeChild(inputs[i]);
     9626                                                        break;
     9627                                                case 'file':
     9628                                                        hasFile = true; // flag the case for later
     9629                                                        break;
     9630                                        }
     9631                                }
     9632                                inputs = [];
     9633
     9634                                if (!hasFile) { // we need to keep the form for sake of possible retries
     9635                                        form.parentNode.removeChild(form);
     9636                                }
     9637                                form = null;
     9638                        }
     9639
     9640                        // without timeout, request is marked as canceled (in console)
     9641                        setTimeout(function() {
     9642                                Events.removeEvent(_iframe, 'load', target.uid);
     9643                                if (_iframe.parentNode) { // #382
     9644                                        _iframe.parentNode.removeChild(_iframe);
     9645                                }
     9646
     9647                                // check if shim container has any other children, if - not, remove it as well
     9648                                var shimContainer = target.getRuntime().getShimContainer();
     9649                                if (!shimContainer.children.length) {
     9650                                        shimContainer.parentNode.removeChild(shimContainer);
     9651                                }
     9652
     9653                                shimContainer = _iframe = null;
     9654                                cb();
     9655                        }, 1);
     9656                }
     9657
     9658                Basic.extend(this, {
     9659                        send: function(meta, data) {
     9660                                var target = this, I = target.getRuntime(), uid, form, input, blob;
     9661
     9662                                _status = _response = null;
     9663
     9664                                function createIframe() {
     9665                                        var container = I.getShimContainer() || document.body
     9666                                        , temp = document.createElement('div')
     9667                                        ;
     9668
     9669                                        // IE 6 won't be able to set the name using setAttribute or iframe.name
     9670                                        temp.innerHTML = '<iframe id="' + uid + '_iframe" name="' + uid + '_iframe" src="javascript:&quot;&quot;" style="display:none"></iframe>';
     9671                                        _iframe = temp.firstChild;
     9672                                        container.appendChild(_iframe);
     9673
     9674                                        /* _iframe.onreadystatechange = function() {
     9675                                                console.info(_iframe.readyState);
     9676                                        };*/
     9677
     9678                                        Events.addEvent(_iframe, 'load', function() { // _iframe.onload doesn't work in IE lte 8
     9679                                                var el;
     9680
     9681                                                try {
     9682                                                        el = _iframe.contentWindow.document || _iframe.contentDocument || window.frames[_iframe.id].document;
     9683
     9684                                                        // try to detect some standard error pages
     9685                                                        if (/^4(0[0-9]|1[0-7]|2[2346])\s/.test(el.title)) { // test if title starts with 4xx HTTP error
     9686                                                                _status = el.title.replace(/^(\d+).*$/, '$1');
     9687                                                        } else {
     9688                                                                _status = 200;
     9689                                                                // get result
     9690                                                                _response = Basic.trim(el.body.innerHTML);
     9691
     9692                                                                // we need to fire these at least once
     9693                                                                target.trigger({
     9694                                                                        type: 'progress',
     9695                                                                        loaded: _response.length,
     9696                                                                        total: _response.length
     9697                                                                });
     9698
     9699                                                                if (blob) { // if we were uploading a file
     9700                                                                        target.trigger({
     9701                                                                                type: 'uploadprogress',
     9702                                                                                loaded: blob.size || 1025,
     9703                                                                                total: blob.size || 1025
     9704                                                                        });
     9705                                                                }
     9706                                                        }
     9707                                                } catch (ex) {
     9708                                                        if (Url.hasSameOrigin(meta.url)) {
     9709                                                                // if response is sent with error code, iframe in IE gets redirected to res://ieframe.dll/http_x.htm
     9710                                                                // which obviously results to cross domain error (wtf?)
     9711                                                                _status = 404;
     9712                                                        } else {
     9713                                                                cleanup.call(target, function() {
     9714                                                                        target.trigger('error');
     9715                                                                });
     9716                                                                return;
     9717                                                        }
     9718                                                }       
     9719                                       
     9720                                                cleanup.call(target, function() {
     9721                                                        target.trigger('load');
     9722                                                });
     9723                                        }, target.uid);
     9724                                } // end createIframe
     9725
     9726                                // prepare data to be sent and convert if required
     9727                                if (data instanceof FormData && data.hasBlob()) {
     9728                                        blob = data.getBlob();
     9729                                        uid = blob.uid;
     9730                                        input = Dom.get(uid);
     9731                                        form = Dom.get(uid + '_form');
     9732                                        if (!form) {
     9733                                                throw new x.DOMException(x.DOMException.NOT_FOUND_ERR);
     9734                                        }
     9735                                } else {
     9736                                        uid = Basic.guid('uid_');
     9737
     9738                                        form = document.createElement('form');
     9739                                        form.setAttribute('id', uid + '_form');
     9740                                        form.setAttribute('method', meta.method);
     9741                                        form.setAttribute('enctype', 'multipart/form-data');
     9742                                        form.setAttribute('encoding', 'multipart/form-data');
     9743
     9744                                        I.getShimContainer().appendChild(form);
     9745                                }
     9746
     9747                                // set upload target
     9748                                form.setAttribute('target', uid + '_iframe');
     9749
     9750                                if (data instanceof FormData) {
     9751                                        data.each(function(value, name) {
     9752                                                if (value instanceof Blob) {
     9753                                                        if (input) {
     9754                                                                input.setAttribute('name', name);
     9755                                                        }
     9756                                                } else {
     9757                                                        var hidden = document.createElement('input');
     9758
     9759                                                        Basic.extend(hidden, {
     9760                                                                type : 'hidden',
     9761                                                                name : name,
     9762                                                                value : value
     9763                                                        });
     9764
     9765                                                        // make sure that input[type="file"], if it's there, comes last
     9766                                                        if (input) {
     9767                                                                form.insertBefore(hidden, input);
     9768                                                        } else {
     9769                                                                form.appendChild(hidden);
     9770                                                        }
     9771                                                }
     9772                                        });
     9773                                }
     9774
     9775                                // set destination url
     9776                                form.setAttribute("action", meta.url);
     9777
     9778                                createIframe();
     9779                                form.submit();
     9780                                target.trigger('loadstart');
     9781                        },
     9782
     9783                        getStatus: function() {
     9784                                return _status;
     9785                        },
     9786
     9787                        getResponse: function(responseType) {
     9788                                if ('json' === responseType) {
     9789                                        // strip off <pre>..</pre> tags that might be enclosing the response
     9790                                        if (Basic.typeOf(_response) === 'string' && !!window.JSON) {
     9791                                                try {
     9792                                                        return JSON.parse(_response.replace(/^\s*<pre[^>]*>/, '').replace(/<\/pre>\s*$/, ''));
     9793                                                } catch (ex) {
     9794                                                        return null;
     9795                                                }
     9796                                        }
     9797                                } else if ('document' === responseType) {
     9798
     9799                                }
     9800                                return _response;
     9801                        },
     9802
     9803                        abort: function() {
     9804                                var target = this;
     9805
     9806                                if (_iframe && _iframe.contentWindow) {
     9807                                        if (_iframe.contentWindow.stop) { // FireFox/Safari/Chrome
     9808                                                _iframe.contentWindow.stop();
     9809                                        } else if (_iframe.contentWindow.document.execCommand) { // IE
     9810                                                _iframe.contentWindow.document.execCommand('Stop');
     9811                                        } else {
     9812                                                _iframe.src = "about:blank";
     9813                                        }
     9814                                }
     9815
     9816                                cleanup.call(this, function() {
     9817                                        // target.dispatchEvent('readystatechange');
     9818                                        target.dispatchEvent('abort');
     9819                                });
     9820                        }
     9821                });
     9822        }
     9823
     9824        return (extensions.XMLHttpRequest = XMLHttpRequest);
     9825});
     9826
     9827// Included from: src/javascript/runtime/html4/image/Image.js
     9828
     9829/**
     9830 * Image.js
     9831 *
     9832 * Copyright 2013, Moxiecode Systems AB
     9833 * Released under GPL License.
     9834 *
     9835 * License: http://www.plupload.com/license
     9836 * Contributing: http://www.plupload.com/contributing
     9837 */
     9838
     9839/**
     9840@class moxie/runtime/html4/image/Image
     9841@private
     9842*/
     9843define("moxie/runtime/html4/image/Image", [
     9844        "moxie/runtime/html4/Runtime",
     9845        "moxie/runtime/html5/image/Image"
     9846], function(extensions, Image) {
     9847        return (extensions.Image = Image);
     9848});
     9849
     9850expose(["moxie/core/utils/Basic","moxie/core/utils/Env","moxie/core/I18n","moxie/core/utils/Mime","moxie/core/utils/Dom","moxie/core/Exceptions","moxie/core/EventTarget","moxie/runtime/Runtime","moxie/runtime/RuntimeClient","moxie/file/FileInput","moxie/core/utils/Encode","moxie/file/Blob","moxie/file/File","moxie/file/FileDrop","moxie/file/FileReader","moxie/core/utils/Url","moxie/runtime/RuntimeTarget","moxie/file/FileReaderSync","moxie/xhr/FormData","moxie/xhr/XMLHttpRequest","moxie/runtime/Transporter","moxie/image/Image","moxie/core/utils/Events"]);
     9851})(this);
     9852/**
     9853 * o.js
     9854 *
     9855 * Copyright 2013, Moxiecode Systems AB
     9856 * Released under GPL License.
     9857 *
     9858 * License: http://www.plupload.com/license
     9859 * Contributing: http://www.plupload.com/contributing
     9860 */
     9861
     9862/*global moxie:true */
     9863
     9864/**
     9865Globally exposed namespace with the most frequently used public classes and handy methods.
     9866
     9867@class o
     9868@static
     9869@private
     9870*/
     9871(function(exports) {
     9872        "use strict";
     9873
     9874        var o = {}, inArray = exports.moxie.core.utils.Basic.inArray;
     9875
     9876        // directly add some public classes
     9877        // (we do it dynamically here, since for custom builds we cannot know beforehand what modules were included)
     9878        (function addAlias(ns) {
     9879                var name, itemType;
     9880                for (name in ns) {
     9881                        itemType = typeof(ns[name]);
     9882                        if (itemType === 'object' && !~inArray(name, ['Exceptions', 'Env', 'Mime'])) {
     9883                                addAlias(ns[name]);
     9884                        } else if (itemType === 'function') {
     9885                                o[name] = ns[name];
     9886                        }
     9887                }
     9888        })(exports.moxie);
     9889
     9890        // add some manually
     9891        o.Env = exports.moxie.core.utils.Env;
     9892        o.Mime = exports.moxie.core.utils.Mime;
     9893        o.Exceptions = exports.moxie.core.Exceptions;
     9894
     9895        // expose globally
     9896        exports.mOxie = o;
     9897        if (!exports.o) {
     9898                exports.o = o;
     9899        }
     9900        return o;
     9901})(this);
  • new file src/js/_enqueues/vendor/plupload/plupload.js

    diff --git a/src/js/_enqueues/vendor/plupload/plupload.js b/src/js/_enqueues/vendor/plupload/plupload.js
    new file mode 100644
    index 0000000000..d562c93494
    - +  
     1/**
     2 * Plupload - multi-runtime File Uploader
     3 * v2.1.9
     4 *
     5 * Copyright 2013, Moxiecode Systems AB
     6 * Released under GPL License.
     7 *
     8 * License: http://www.plupload.com/license
     9 * Contributing: http://www.plupload.com/contributing
     10 *
     11 * Date: 2016-05-15
     12 */
     13/**
     14 * Plupload.js
     15 *
     16 * Copyright 2013, Moxiecode Systems AB
     17 * Released under GPL License.
     18 *
     19 * License: http://www.plupload.com/license
     20 * Contributing: http://www.plupload.com/contributing
     21 */
     22
     23/**
     24 * Modified for WordPress, Silverlight and Flash runtimes support was removed.
     25 * See https://core.trac.wordpress.org/ticket/41755.
     26 */
     27
     28/*global mOxie:true */
     29
     30;(function(window, o, undef) {
     31
     32var delay = window.setTimeout
     33, fileFilters = {}
     34;
     35
     36// convert plupload features to caps acceptable by mOxie
     37function normalizeCaps(settings) {             
     38        var features = settings.required_features, caps = {};
     39
     40        function resolve(feature, value, strict) {
     41                // Feature notation is deprecated, use caps (this thing here is required for backward compatibility)
     42                var map = {
     43                        chunks: 'slice_blob',
     44                        jpgresize: 'send_binary_string',
     45                        pngresize: 'send_binary_string',
     46                        progress: 'report_upload_progress',
     47                        multi_selection: 'select_multiple',
     48                        dragdrop: 'drag_and_drop',
     49                        drop_element: 'drag_and_drop',
     50                        headers: 'send_custom_headers',
     51                        urlstream_upload: 'send_binary_string',
     52                        canSendBinary: 'send_binary',
     53                        triggerDialog: 'summon_file_dialog'
     54                };
     55
     56                if (map[feature]) {
     57                        caps[map[feature]] = value;
     58                } else if (!strict) {
     59                        caps[feature] = value;
     60                }
     61        }
     62
     63        if (typeof(features) === 'string') {
     64                plupload.each(features.split(/\s*,\s*/), function(feature) {
     65                        resolve(feature, true);
     66                });
     67        } else if (typeof(features) === 'object') {
     68                plupload.each(features, function(value, feature) {
     69                        resolve(feature, value);
     70                });
     71        } else if (features === true) {
     72                // check settings for required features
     73                if (settings.chunk_size > 0) {
     74                        caps.slice_blob = true;
     75                }
     76
     77                if (settings.resize.enabled || !settings.multipart) {
     78                        caps.send_binary_string = true;
     79                }
     80               
     81                plupload.each(settings, function(value, feature) {
     82                        resolve(feature, !!value, true); // strict check
     83                });
     84        }
     85
     86        // WP: only html runtimes.
     87        settings.runtimes = 'html5,html4';
     88
     89        return caps;
     90}
     91
     92/**
     93 * @module plupload     
     94 * @static
     95 */
     96var plupload = {
     97        /**
     98         * Plupload version will be replaced on build.
     99         *
     100         * @property VERSION
     101         * @for Plupload
     102         * @static
     103         * @final
     104         */
     105        VERSION : '2.1.9',
     106
     107        /**
     108         * The state of the queue before it has started and after it has finished
     109         *
     110         * @property STOPPED
     111         * @static
     112         * @final
     113         */
     114        STOPPED : 1,
     115
     116        /**
     117         * Upload process is running
     118         *
     119         * @property STARTED
     120         * @static
     121         * @final
     122         */
     123        STARTED : 2,
     124
     125        /**
     126         * File is queued for upload
     127         *
     128         * @property QUEUED
     129         * @static
     130         * @final
     131         */
     132        QUEUED : 1,
     133
     134        /**
     135         * File is being uploaded
     136         *
     137         * @property UPLOADING
     138         * @static
     139         * @final
     140         */
     141        UPLOADING : 2,
     142
     143        /**
     144         * File has failed to be uploaded
     145         *
     146         * @property FAILED
     147         * @static
     148         * @final
     149         */
     150        FAILED : 4,
     151
     152        /**
     153         * File has been uploaded successfully
     154         *
     155         * @property DONE
     156         * @static
     157         * @final
     158         */
     159        DONE : 5,
     160
     161        // Error constants used by the Error event
     162
     163        /**
     164         * Generic error for example if an exception is thrown inside Silverlight.
     165         *
     166         * @property GENERIC_ERROR
     167         * @static
     168         * @final
     169         */
     170        GENERIC_ERROR : -100,
     171
     172        /**
     173         * HTTP transport error. For example if the server produces a HTTP status other than 200.
     174         *
     175         * @property HTTP_ERROR
     176         * @static
     177         * @final
     178         */
     179        HTTP_ERROR : -200,
     180
     181        /**
     182         * Generic I/O error. For example if it wasn't possible to open the file stream on local machine.
     183         *
     184         * @property IO_ERROR
     185         * @static
     186         * @final
     187         */
     188        IO_ERROR : -300,
     189
     190        /**
     191         * @property SECURITY_ERROR
     192         * @static
     193         * @final
     194         */
     195        SECURITY_ERROR : -400,
     196
     197        /**
     198         * Initialization error. Will be triggered if no runtime was initialized.
     199         *
     200         * @property INIT_ERROR
     201         * @static
     202         * @final
     203         */
     204        INIT_ERROR : -500,
     205
     206        /**
     207         * File size error. If the user selects a file that is too large it will be blocked and an error of this type will be triggered.
     208         *
     209         * @property FILE_SIZE_ERROR
     210         * @static
     211         * @final
     212         */
     213        FILE_SIZE_ERROR : -600,
     214
     215        /**
     216         * File extension error. If the user selects a file that isn't valid according to the filters setting.
     217         *
     218         * @property FILE_EXTENSION_ERROR
     219         * @static
     220         * @final
     221         */
     222        FILE_EXTENSION_ERROR : -601,
     223
     224        /**
     225         * Duplicate file error. If prevent_duplicates is set to true and user selects the same file again.
     226         *
     227         * @property FILE_DUPLICATE_ERROR
     228         * @static
     229         * @final
     230         */
     231        FILE_DUPLICATE_ERROR : -602,
     232
     233        /**
     234         * Runtime will try to detect if image is proper one. Otherwise will throw this error.
     235         *
     236         * @property IMAGE_FORMAT_ERROR
     237         * @static
     238         * @final
     239         */
     240        IMAGE_FORMAT_ERROR : -700,
     241
     242        /**
     243         * While working on files runtime may run out of memory and will throw this error.
     244         *
     245         * @since 2.1.2
     246         * @property MEMORY_ERROR
     247         * @static
     248         * @final
     249         */
     250        MEMORY_ERROR : -701,
     251
     252        /**
     253         * Each runtime has an upper limit on a dimension of the image it can handle. If bigger, will throw this error.
     254         *
     255         * @property IMAGE_DIMENSIONS_ERROR
     256         * @static
     257         * @final
     258         */
     259        IMAGE_DIMENSIONS_ERROR : -702,
     260
     261        /**
     262         * Mime type lookup table.
     263         *
     264         * @property mimeTypes
     265         * @type Object
     266         * @final
     267         */
     268        mimeTypes : o.mimes,
     269
     270        /**
     271         * In some cases sniffing is the only way around :(
     272         */
     273        ua: o.ua,
     274
     275        /**
     276         * Gets the true type of the built-in object (better version of typeof).
     277         * @credits Angus Croll (http://javascriptweblog.wordpress.com/)
     278         *
     279         * @method typeOf
     280         * @static
     281         * @param {Object} o Object to check.
     282         * @return {String} Object [[Class]]
     283         */
     284        typeOf: o.typeOf,
     285
     286        /**
     287         * Extends the specified object with another object.
     288         *
     289         * @method extend
     290         * @static
     291         * @param {Object} target Object to extend.
     292         * @param {Object..} obj Multiple objects to extend with.
     293         * @return {Object} Same as target, the extended object.
     294         */
     295        extend : o.extend,
     296
     297        /**
     298         * Generates an unique ID. This is 99.99% unique since it takes the current time and 5 random numbers.
     299         * The only way a user would be able to get the same ID is if the two persons at the same exact millisecond manages
     300         * to get 5 the same random numbers between 0-65535 it also uses a counter so each call will be guaranteed to be page unique.
     301         * It's more probable for the earth to be hit with an asteriod. You can also if you want to be 100% sure set the plupload.guidPrefix property
     302         * to an user unique key.
     303         *
     304         * @method guid
     305         * @static
     306         * @return {String} Virtually unique id.
     307         */
     308        guid : o.guid,
     309
     310        /**
     311         * Get array of DOM Elements by their ids.
     312         *
     313         * @method get
     314         * @param {String} id Identifier of the DOM Element
     315         * @return {Array}
     316        */
     317        getAll : function get(ids) {
     318                var els = [], el;
     319
     320                if (plupload.typeOf(ids) !== 'array') {
     321                        ids = [ids];
     322                }
     323
     324                var i = ids.length;
     325                while (i--) {
     326                        el = plupload.get(ids[i]);
     327                        if (el) {
     328                                els.push(el);
     329                        }
     330                }
     331
     332                return els.length ? els : null;
     333        },
     334
     335        /**
     336        Get DOM element by id
     337
     338        @method get
     339        @param {String} id Identifier of the DOM Element
     340        @return {Node}
     341        */
     342        get: o.get,
     343
     344        /**
     345         * Executes the callback function for each item in array/object. If you return false in the
     346         * callback it will break the loop.
     347         *
     348         * @method each
     349         * @static
     350         * @param {Object} obj Object to iterate.
     351         * @param {function} callback Callback function to execute for each item.
     352         */
     353        each : o.each,
     354
     355        /**
     356         * Returns the absolute x, y position of an Element. The position will be returned in a object with x, y fields.
     357         *
     358         * @method getPos
     359         * @static
     360         * @param {Element} node HTML element or element id to get x, y position from.
     361         * @param {Element} root Optional root element to stop calculations at.
     362         * @return {object} Absolute position of the specified element object with x, y fields.
     363         */
     364        getPos : o.getPos,
     365
     366        /**
     367         * Returns the size of the specified node in pixels.
     368         *
     369         * @method getSize
     370         * @static
     371         * @param {Node} node Node to get the size of.
     372         * @return {Object} Object with a w and h property.
     373         */
     374        getSize : o.getSize,
     375
     376        /**
     377         * Encodes the specified string.
     378         *
     379         * @method xmlEncode
     380         * @static
     381         * @param {String} s String to encode.
     382         * @return {String} Encoded string.
     383         */
     384        xmlEncode : function(str) {
     385                var xmlEncodeChars = {'<' : 'lt', '>' : 'gt', '&' : 'amp', '"' : 'quot', '\'' : '#39'}, xmlEncodeRegExp = /[<>&\"\']/g;
     386
     387                return str ? ('' + str).replace(xmlEncodeRegExp, function(chr) {
     388                        return xmlEncodeChars[chr] ? '&' + xmlEncodeChars[chr] + ';' : chr;
     389                }) : str;
     390        },
     391
     392        /**
     393         * Forces anything into an array.
     394         *
     395         * @method toArray
     396         * @static
     397         * @param {Object} obj Object with length field.
     398         * @return {Array} Array object containing all items.
     399         */
     400        toArray : o.toArray,
     401
     402        /**
     403         * Find an element in array and return its index if present, otherwise return -1.
     404         *
     405         * @method inArray
     406         * @static
     407         * @param {mixed} needle Element to find
     408         * @param {Array} array
     409         * @return {Int} Index of the element, or -1 if not found
     410         */
     411        inArray : o.inArray,
     412
     413        /**
     414         * Extends the language pack object with new items.
     415         *
     416         * @method addI18n
     417         * @static
     418         * @param {Object} pack Language pack items to add.
     419         * @return {Object} Extended language pack object.
     420         */
     421        addI18n : o.addI18n,
     422
     423        /**
     424         * Translates the specified string by checking for the english string in the language pack lookup.
     425         *
     426         * @method translate
     427         * @static
     428         * @param {String} str String to look for.
     429         * @return {String} Translated string or the input string if it wasn't found.
     430         */
     431        translate : o.translate,
     432
     433        /**
     434         * Checks if object is empty.
     435         *
     436         * @method isEmptyObj
     437         * @static
     438         * @param {Object} obj Object to check.
     439         * @return {Boolean}
     440         */
     441        isEmptyObj : o.isEmptyObj,
     442
     443        /**
     444         * Checks if specified DOM element has specified class.
     445         *
     446         * @method hasClass
     447         * @static
     448         * @param {Object} obj DOM element like object to add handler to.
     449         * @param {String} name Class name
     450         */
     451        hasClass : o.hasClass,
     452
     453        /**
     454         * Adds specified className to specified DOM element.
     455         *
     456         * @method addClass
     457         * @static
     458         * @param {Object} obj DOM element like object to add handler to.
     459         * @param {String} name Class name
     460         */
     461        addClass : o.addClass,
     462
     463        /**
     464         * Removes specified className from specified DOM element.
     465         *
     466         * @method removeClass
     467         * @static
     468         * @param {Object} obj DOM element like object to add handler to.
     469         * @param {String} name Class name
     470         */
     471        removeClass : o.removeClass,
     472
     473        /**
     474         * Returns a given computed style of a DOM element.
     475         *
     476         * @method getStyle
     477         * @static
     478         * @param {Object} obj DOM element like object.
     479         * @param {String} name Style you want to get from the DOM element
     480         */
     481        getStyle : o.getStyle,
     482
     483        /**
     484         * Adds an event handler to the specified object and store reference to the handler
     485         * in objects internal Plupload registry (@see removeEvent).
     486         *
     487         * @method addEvent
     488         * @static
     489         * @param {Object} obj DOM element like object to add handler to.
     490         * @param {String} name Name to add event listener to.
     491         * @param {Function} callback Function to call when event occurs.
     492         * @param {String} (optional) key that might be used to add specifity to the event record.
     493         */
     494        addEvent : o.addEvent,
     495
     496        /**
     497         * Remove event handler from the specified object. If third argument (callback)
     498         * is not specified remove all events with the specified name.
     499         *
     500         * @method removeEvent
     501         * @static
     502         * @param {Object} obj DOM element to remove event listener(s) from.
     503         * @param {String} name Name of event listener to remove.
     504         * @param {Function|String} (optional) might be a callback or unique key to match.
     505         */
     506        removeEvent: o.removeEvent,
     507
     508        /**
     509         * Remove all kind of events from the specified object
     510         *
     511         * @method removeAllEvents
     512         * @static
     513         * @param {Object} obj DOM element to remove event listeners from.
     514         * @param {String} (optional) unique key to match, when removing events.
     515         */
     516        removeAllEvents: o.removeAllEvents,
     517
     518        /**
     519         * Cleans the specified name from national characters (diacritics). The result will be a name with only a-z, 0-9 and _.
     520         *
     521         * @method cleanName
     522         * @static
     523         * @param {String} s String to clean up.
     524         * @return {String} Cleaned string.
     525         */
     526        cleanName : function(name) {
     527                var i, lookup;
     528
     529                // Replace diacritics
     530                lookup = [
     531                        /[\300-\306]/g, 'A', /[\340-\346]/g, 'a',
     532                        /\307/g, 'C', /\347/g, 'c',
     533                        /[\310-\313]/g, 'E', /[\350-\353]/g, 'e',
     534                        /[\314-\317]/g, 'I', /[\354-\357]/g, 'i',
     535                        /\321/g, 'N', /\361/g, 'n',
     536                        /[\322-\330]/g, 'O', /[\362-\370]/g, 'o',
     537                        /[\331-\334]/g, 'U', /[\371-\374]/g, 'u'
     538                ];
     539
     540                for (i = 0; i < lookup.length; i += 2) {
     541                        name = name.replace(lookup[i], lookup[i + 1]);
     542                }
     543
     544                // Replace whitespace
     545                name = name.replace(/\s+/g, '_');
     546
     547                // Remove anything else
     548                name = name.replace(/[^a-z0-9_\-\.]+/gi, '');
     549
     550                return name;
     551        },
     552
     553        /**
     554         * Builds a full url out of a base URL and an object with items to append as query string items.
     555         *
     556         * @method buildUrl
     557         * @static
     558         * @param {String} url Base URL to append query string items to.
     559         * @param {Object} items Name/value object to serialize as a querystring.
     560         * @return {String} String with url + serialized query string items.
     561         */
     562        buildUrl : function(url, items) {
     563                var query = '';
     564
     565                plupload.each(items, function(value, name) {
     566                        query += (query ? '&' : '') + encodeURIComponent(name) + '=' + encodeURIComponent(value);
     567                });
     568
     569                if (query) {
     570                        url += (url.indexOf('?') > 0 ? '&' : '?') + query;
     571                }
     572
     573                return url;
     574        },
     575
     576        /**
     577         * Formats the specified number as a size string for example 1024 becomes 1 KB.
     578         *
     579         * @method formatSize
     580         * @static
     581         * @param {Number} size Size to format as string.
     582         * @return {String} Formatted size string.
     583         */
     584        formatSize : function(size) {
     585
     586                if (size === undef || /\D/.test(size)) {
     587                        return plupload.translate('N/A');
     588                }
     589
     590                function round(num, precision) {
     591                        return Math.round(num * Math.pow(10, precision)) / Math.pow(10, precision);
     592                }
     593
     594                var boundary = Math.pow(1024, 4);
     595
     596                // TB
     597                if (size > boundary) {
     598                        return round(size / boundary, 1) + " " + plupload.translate('tb');
     599                }
     600
     601                // GB
     602                if (size > (boundary/=1024)) {
     603                        return round(size / boundary, 1) + " " + plupload.translate('gb');
     604                }
     605
     606                // MB
     607                if (size > (boundary/=1024)) {
     608                        return round(size / boundary, 1) + " " + plupload.translate('mb');
     609                }
     610
     611                // KB
     612                if (size > 1024) {
     613                        return Math.round(size / 1024) + " " + plupload.translate('kb');
     614                }
     615
     616                return size + " " + plupload.translate('b');
     617        },
     618
     619
     620        /**
     621         * Parses the specified size string into a byte value. For example 10kb becomes 10240.
     622         *
     623         * @method parseSize
     624         * @static
     625         * @param {String|Number} size String to parse or number to just pass through.
     626         * @return {Number} Size in bytes.
     627         */
     628        parseSize : o.parseSizeStr,
     629
     630
     631        /**
     632         * A way to predict what runtime will be choosen in the current environment with the
     633         * specified settings.
     634         *
     635         * @method predictRuntime
     636         * @static
     637         * @param {Object|String} config Plupload settings to check
     638         * @param {String} [runtimes] Comma-separated list of runtimes to check against
     639         * @return {String} Type of compatible runtime
     640         */
     641        predictRuntime : function(config, runtimes) {
     642                var up, runtime;
     643
     644                up = new plupload.Uploader(config);
     645                runtime = o.Runtime.thatCan(up.getOption().required_features, runtimes || config.runtimes);
     646                up.destroy();
     647                return runtime;
     648        },
     649
     650        /**
     651         * Registers a filter that will be executed for each file added to the queue.
     652         * If callback returns false, file will not be added.
     653         *
     654         * Callback receives two arguments: a value for the filter as it was specified in settings.filters
     655         * and a file to be filtered. Callback is executed in the context of uploader instance.
     656         *
     657         * @method addFileFilter
     658         * @static
     659         * @param {String} name Name of the filter by which it can be referenced in settings.filters
     660         * @param {String} cb Callback - the actual routine that every added file must pass
     661         */
     662        addFileFilter: function(name, cb) {
     663                fileFilters[name] = cb;
     664        }
     665};
     666
     667
     668plupload.addFileFilter('mime_types', function(filters, file, cb) {
     669        if (filters.length && !filters.regexp.test(file.name)) {
     670                this.trigger('Error', {
     671                        code : plupload.FILE_EXTENSION_ERROR,
     672                        message : plupload.translate('File extension error.'),
     673                        file : file
     674                });
     675                cb(false);
     676        } else {
     677                cb(true);
     678        }
     679});
     680
     681
     682plupload.addFileFilter('max_file_size', function(maxSize, file, cb) {
     683        var undef;
     684
     685        maxSize = plupload.parseSize(maxSize);
     686
     687        // Invalid file size
     688        if (file.size !== undef && maxSize && file.size > maxSize) {
     689                this.trigger('Error', {
     690                        code : plupload.FILE_SIZE_ERROR,
     691                        message : plupload.translate('File size error.'),
     692                        file : file
     693                });
     694                cb(false);
     695        } else {
     696                cb(true);
     697        }
     698});
     699
     700
     701plupload.addFileFilter('prevent_duplicates', function(value, file, cb) {
     702        if (value) {
     703                var ii = this.files.length;
     704                while (ii--) {
     705                        // Compare by name and size (size might be 0 or undefined, but still equivalent for both)
     706                        if (file.name === this.files[ii].name && file.size === this.files[ii].size) {
     707                                this.trigger('Error', {
     708                                        code : plupload.FILE_DUPLICATE_ERROR,
     709                                        message : plupload.translate('Duplicate file error.'),
     710                                        file : file
     711                                });
     712                                cb(false);
     713                                return;
     714                        }
     715                }
     716        }
     717        cb(true);
     718});
     719
     720
     721/**
     722@class Uploader
     723@constructor
     724
     725@param {Object} settings For detailed information about each option check documentation.
     726        @param {String|DOMElement} settings.browse_button id of the DOM element or DOM element itself to use as file dialog trigger.
     727        @param {String} settings.url URL of the server-side upload handler.
     728        @param {Number|String} [settings.chunk_size=0] Chunk size in bytes to slice the file into. Shorcuts with b, kb, mb, gb, tb suffixes also supported. `e.g. 204800 or "204800b" or "200kb"`. By default - disabled.
     729        @param {Boolean} [settings.send_chunk_number=true] Whether to send chunks and chunk numbers, or total and offset bytes.
     730        @param {String|DOMElement} [settings.container] id of the DOM element or DOM element itself that will be used to wrap uploader structures. Defaults to immediate parent of the `browse_button` element.
     731        @param {String|DOMElement} [settings.drop_element] id of the DOM element or DOM element itself to use as a drop zone for Drag-n-Drop.
     732        @param {String} [settings.file_data_name="file"] Name for the file field in Multipart formated message.
     733        @param {Object} [settings.filters={}] Set of file type filters.
     734                @param {Array} [settings.filters.mime_types=[]] List of file types to accept, each one defined by title and list of extensions. `e.g. {title : "Image files", extensions : "jpg,jpeg,gif,png"}`. Dispatches `plupload.FILE_EXTENSION_ERROR`
     735                @param {String|Number} [settings.filters.max_file_size=0] Maximum file size that the user can pick, in bytes. Optionally supports b, kb, mb, gb, tb suffixes. `e.g. "10mb" or "1gb"`. By default - not set. Dispatches `plupload.FILE_SIZE_ERROR`.
     736                @param {Boolean} [settings.filters.prevent_duplicates=false] Do not let duplicates into the queue. Dispatches `plupload.FILE_DUPLICATE_ERROR`.
     737        @param {String} [settings.flash_swf_url] URL of the Flash swf. (Not used in WordPress)
     738        @param {Object} [settings.headers] Custom headers to send with the upload. Hash of name/value pairs.
     739        @param {Number} [settings.max_retries=0] How many times to retry the chunk or file, before triggering Error event.
     740        @param {Boolean} [settings.multipart=true] Whether to send file and additional parameters as Multipart formated message.
     741        @param {Object} [settings.multipart_params] Hash of key/value pairs to send with every file upload.
     742        @param {Boolean} [settings.multi_selection=true] Enable ability to select multiple files at once in file dialog.
     743        @param {String|Object} [settings.required_features] Either comma-separated list or hash of required features that chosen runtime should absolutely possess.
     744        @param {Object} [settings.resize] Enable resizng of images on client-side. Applies to `image/jpeg` and `image/png` only. `e.g. {width : 200, height : 200, quality : 90, crop: true}`
     745                @param {Number} [settings.resize.width] If image is bigger, it will be resized.
     746                @param {Number} [settings.resize.height] If image is bigger, it will be resized.
     747                @param {Number} [settings.resize.quality=90] Compression quality for jpegs (1-100).
     748                @param {Boolean} [settings.resize.crop=false] Whether to crop images to exact dimensions. By default they will be resized proportionally.
     749        @param {String} [settings.runtimes="html5,html4"] Comma separated list of runtimes, that Plupload will try in turn, moving to the next if previous fails.
     750        @param {String} [settings.silverlight_xap_url] URL of the Silverlight xap. (Not used in WordPress)
     751        @param {Boolean} [settings.unique_names=false] If true will generate unique filenames for uploaded files.
     752        @param {Boolean} [settings.send_file_name=true] Whether to send file name as additional argument - 'name' (required for chunked uploads and some other cases where file name cannot be sent via normal ways).
     753*/
     754plupload.Uploader = function(options) {
     755        /**
     756        Fires when the current RunTime has been initialized.
     757       
     758        @event Init
     759        @param {plupload.Uploader} uploader Uploader instance sending the event.
     760         */
     761
     762        /**
     763        Fires after the init event incase you need to perform actions there.
     764       
     765        @event PostInit
     766        @param {plupload.Uploader} uploader Uploader instance sending the event.
     767         */
     768
     769        /**
     770        Fires when the option is changed in via uploader.setOption().
     771       
     772        @event OptionChanged
     773        @since 2.1
     774        @param {plupload.Uploader} uploader Uploader instance sending the event.
     775        @param {String} name Name of the option that was changed
     776        @param {Mixed} value New value for the specified option
     777        @param {Mixed} oldValue Previous value of the option
     778         */
     779
     780        /**
     781        Fires when the silverlight/flash or other shim needs to move.
     782       
     783        @event Refresh
     784        @param {plupload.Uploader} uploader Uploader instance sending the event.
     785         */
     786
     787        /**
     788        Fires when the overall state is being changed for the upload queue.
     789       
     790        @event StateChanged
     791        @param {plupload.Uploader} uploader Uploader instance sending the event.
     792         */
     793
     794        /**
     795        Fires when browse_button is clicked and browse dialog shows.
     796       
     797        @event Browse
     798        @since 2.1.2
     799        @param {plupload.Uploader} uploader Uploader instance sending the event.
     800         */     
     801
     802        /**
     803        Fires for every filtered file before it is added to the queue.
     804       
     805        @event FileFiltered
     806        @since 2.1
     807        @param {plupload.Uploader} uploader Uploader instance sending the event.
     808        @param {plupload.File} file Another file that has to be added to the queue.
     809         */
     810
     811        /**
     812        Fires when the file queue is changed. In other words when files are added/removed to the files array of the uploader instance.
     813       
     814        @event QueueChanged
     815        @param {plupload.Uploader} uploader Uploader instance sending the event.
     816         */
     817
     818        /**
     819        Fires after files were filtered and added to the queue.
     820       
     821        @event FilesAdded
     822        @param {plupload.Uploader} uploader Uploader instance sending the event.
     823        @param {Array} files Array of file objects that were added to queue by the user.
     824         */
     825
     826        /**
     827        Fires when file is removed from the queue.
     828       
     829        @event FilesRemoved
     830        @param {plupload.Uploader} uploader Uploader instance sending the event.
     831        @param {Array} files Array of files that got removed.
     832         */
     833
     834        /**
     835        Fires just before a file is uploaded. Can be used to cancel the upload for the specified file
     836        by returning false from the handler.
     837       
     838        @event BeforeUpload
     839        @param {plupload.Uploader} uploader Uploader instance sending the event.
     840        @param {plupload.File} file File to be uploaded.
     841         */
     842
     843        /**
     844        Fires when a file is to be uploaded by the runtime.
     845       
     846        @event UploadFile
     847        @param {plupload.Uploader} uploader Uploader instance sending the event.
     848        @param {plupload.File} file File to be uploaded.
     849         */
     850
     851        /**
     852        Fires while a file is being uploaded. Use this event to update the current file upload progress.
     853       
     854        @event UploadProgress
     855        @param {plupload.Uploader} uploader Uploader instance sending the event.
     856        @param {plupload.File} file File that is currently being uploaded.
     857         */     
     858
     859        /**
     860        Fires when file chunk is uploaded.
     861       
     862        @event ChunkUploaded
     863        @param {plupload.Uploader} uploader Uploader instance sending the event.
     864        @param {plupload.File} file File that the chunk was uploaded for.
     865        @param {Object} result Object with response properties.
     866                @param {Number} result.offset The amount of bytes the server has received so far, including this chunk.
     867                @param {Number} result.total The size of the file.
     868                @param {String} result.response The response body sent by the server.
     869                @param {Number} result.status The HTTP status code sent by the server.
     870                @param {String} result.responseHeaders All the response headers as a single string.
     871         */
     872
     873        /**
     874        Fires when a file is successfully uploaded.
     875       
     876        @event FileUploaded
     877        @param {plupload.Uploader} uploader Uploader instance sending the event.
     878        @param {plupload.File} file File that was uploaded.
     879        @param {Object} result Object with response properties.
     880                @param {String} result.response The response body sent by the server.
     881                @param {Number} result.status The HTTP status code sent by the server.
     882                @param {String} result.responseHeaders All the response headers as a single string.
     883         */
     884
     885        /**
     886        Fires when all files in a queue are uploaded.
     887       
     888        @event UploadComplete
     889        @param {plupload.Uploader} uploader Uploader instance sending the event.
     890        @param {Array} files Array of file objects that was added to queue/selected by the user.
     891         */
     892
     893        /**
     894        Fires when a error occurs.
     895       
     896        @event Error
     897        @param {plupload.Uploader} uploader Uploader instance sending the event.
     898        @param {Object} error Contains code, message and sometimes file and other details.
     899                @param {Number} error.code The plupload error code.
     900                @param {String} error.message Description of the error (uses i18n).
     901         */
     902
     903        /**
     904        Fires when destroy method is called.
     905       
     906        @event Destroy
     907        @param {plupload.Uploader} uploader Uploader instance sending the event.
     908         */
     909        var uid = plupload.guid()
     910        , settings
     911        , files = []
     912        , preferred_caps = {}
     913        , fileInputs = []
     914        , fileDrops = []
     915        , startTime
     916        , total
     917        , disabled = false
     918        , xhr
     919        ;
     920
     921
     922        // Private methods
     923        function uploadNext() {
     924                var file, count = 0, i;
     925
     926                if (this.state == plupload.STARTED) {
     927                        // Find first QUEUED file
     928                        for (i = 0; i < files.length; i++) {
     929                                if (!file && files[i].status == plupload.QUEUED) {
     930                                        file = files[i];
     931                                        if (this.trigger("BeforeUpload", file)) {
     932                                                file.status = plupload.UPLOADING;
     933                                                this.trigger("UploadFile", file);
     934                                        }
     935                                } else {
     936                                        count++;
     937                                }
     938                        }
     939
     940                        // All files are DONE or FAILED
     941                        if (count == files.length) {
     942                                if (this.state !== plupload.STOPPED) {
     943                                        this.state = plupload.STOPPED;
     944                                        this.trigger("StateChanged");
     945                                }
     946                                this.trigger("UploadComplete", files);
     947                        }
     948                }
     949        }
     950
     951
     952        function calcFile(file) {
     953                file.percent = file.size > 0 ? Math.ceil(file.loaded / file.size * 100) : 100;
     954                calc();
     955        }
     956
     957
     958        function calc() {
     959                var i, file;
     960
     961                // Reset stats
     962                total.reset();
     963
     964                // Check status, size, loaded etc on all files
     965                for (i = 0; i < files.length; i++) {
     966                        file = files[i];
     967
     968                        if (file.size !== undef) {
     969                                // We calculate totals based on original file size
     970                                total.size += file.origSize;
     971
     972                                // Since we cannot predict file size after resize, we do opposite and
     973                                // interpolate loaded amount to match magnitude of total
     974                                total.loaded += file.loaded * file.origSize / file.size;
     975                        } else {
     976                                total.size = undef;
     977                        }
     978
     979                        if (file.status == plupload.DONE) {
     980                                total.uploaded++;
     981                        } else if (file.status == plupload.FAILED) {
     982                                total.failed++;
     983                        } else {
     984                                total.queued++;
     985                        }
     986                }
     987
     988                // If we couldn't calculate a total file size then use the number of files to calc percent
     989                if (total.size === undef) {
     990                        total.percent = files.length > 0 ? Math.ceil(total.uploaded / files.length * 100) : 0;
     991                } else {
     992                        total.bytesPerSec = Math.ceil(total.loaded / ((+new Date() - startTime || 1) / 1000.0));
     993                        total.percent = total.size > 0 ? Math.ceil(total.loaded / total.size * 100) : 0;
     994                }
     995        }
     996
     997
     998        function getRUID() {
     999                var ctrl = fileInputs[0] || fileDrops[0];
     1000                if (ctrl) {
     1001                        return ctrl.getRuntime().uid;
     1002                }
     1003                return false;
     1004        }
     1005
     1006
     1007        function runtimeCan(file, cap) {
     1008                if (file.ruid) {
     1009                        var info = o.Runtime.getInfo(file.ruid);
     1010                        if (info) {
     1011                                return info.can(cap);
     1012                        }
     1013                }
     1014                return false;
     1015        }
     1016
     1017
     1018        function bindEventListeners() {
     1019                this.bind('FilesAdded FilesRemoved', function(up) {
     1020                        up.trigger('QueueChanged');
     1021                        up.refresh();
     1022                });
     1023
     1024                this.bind('CancelUpload', onCancelUpload);
     1025               
     1026                this.bind('BeforeUpload', onBeforeUpload);
     1027
     1028                this.bind('UploadFile', onUploadFile);
     1029
     1030                this.bind('UploadProgress', onUploadProgress);
     1031
     1032                this.bind('StateChanged', onStateChanged);
     1033
     1034                this.bind('QueueChanged', calc);
     1035
     1036                this.bind('Error', onError);
     1037
     1038                this.bind('FileUploaded', onFileUploaded);
     1039
     1040                this.bind('Destroy', onDestroy);
     1041        }
     1042
     1043
     1044        function initControls(settings, cb) {
     1045                var self = this, inited = 0, queue = [];
     1046
     1047                // common settings
     1048                var options = {
     1049                        runtime_order: settings.runtimes,
     1050                        required_caps: settings.required_features,
     1051                        preferred_caps: preferred_caps
     1052                };
     1053
     1054                // add runtime specific options if any
     1055                plupload.each(settings.runtimes.split(/\s*,\s*/), function(runtime) {
     1056                        if (settings[runtime]) {
     1057                                options[runtime] = settings[runtime];
     1058                        }
     1059                });
     1060
     1061                // initialize file pickers - there can be many
     1062                if (settings.browse_button) {
     1063                        plupload.each(settings.browse_button, function(el) {
     1064                                queue.push(function(cb) {
     1065                                        var fileInput = new o.FileInput(plupload.extend({}, options, {
     1066                                                accept: settings.filters.mime_types,
     1067                                                name: settings.file_data_name,
     1068                                                multiple: settings.multi_selection,
     1069                                                container: settings.container,
     1070                                                browse_button: el
     1071                                        }));
     1072
     1073                                        fileInput.onready = function() {
     1074                                                var info = o.Runtime.getInfo(this.ruid);
     1075
     1076                                                // for backward compatibility
     1077                                                o.extend(self.features, {
     1078                                                        chunks: info.can('slice_blob'),
     1079                                                        multipart: info.can('send_multipart'),
     1080                                                        multi_selection: info.can('select_multiple')
     1081                                                });
     1082
     1083                                                inited++;
     1084                                                fileInputs.push(this);
     1085                                                cb();
     1086                                        };
     1087
     1088                                        fileInput.onchange = function() {
     1089                                                self.addFile(this.files);
     1090                                        };
     1091
     1092                                        fileInput.bind('mouseenter mouseleave mousedown mouseup', function(e) {
     1093                                                if (!disabled) {
     1094                                                        if (settings.browse_button_hover) {
     1095                                                                if ('mouseenter' === e.type) {
     1096                                                                        o.addClass(el, settings.browse_button_hover);
     1097                                                                } else if ('mouseleave' === e.type) {
     1098                                                                        o.removeClass(el, settings.browse_button_hover);
     1099                                                                }
     1100                                                        }
     1101
     1102                                                        if (settings.browse_button_active) {
     1103                                                                if ('mousedown' === e.type) {
     1104                                                                        o.addClass(el, settings.browse_button_active);
     1105                                                                } else if ('mouseup' === e.type) {
     1106                                                                        o.removeClass(el, settings.browse_button_active);
     1107                                                                }
     1108                                                        }
     1109                                                }
     1110                                        });
     1111
     1112                                        fileInput.bind('mousedown', function() {
     1113                                                self.trigger('Browse');
     1114                                        });
     1115
     1116                                        fileInput.bind('error runtimeerror', function() {
     1117                                                fileInput = null;
     1118                                                cb();
     1119                                        });
     1120
     1121                                        fileInput.init();
     1122                                });
     1123                        });
     1124                }
     1125
     1126                // initialize drop zones
     1127                if (settings.drop_element) {
     1128                        plupload.each(settings.drop_element, function(el) {
     1129                                queue.push(function(cb) {
     1130                                        var fileDrop = new o.FileDrop(plupload.extend({}, options, {
     1131                                                drop_zone: el
     1132                                        }));
     1133
     1134                                        fileDrop.onready = function() {
     1135                                                var info = o.Runtime.getInfo(this.ruid);
     1136
     1137                                                // for backward compatibility
     1138                                                o.extend(self.features, {
     1139                                                        chunks: info.can('slice_blob'),
     1140                                                        multipart: info.can('send_multipart'),
     1141                                                        dragdrop: info.can('drag_and_drop')
     1142                                                });
     1143
     1144                                                inited++;
     1145                                                fileDrops.push(this);
     1146                                                cb();
     1147                                        };
     1148
     1149                                        fileDrop.ondrop = function() {
     1150                                                self.addFile(this.files);
     1151                                        };
     1152
     1153                                        fileDrop.bind('error runtimeerror', function() {
     1154                                                fileDrop = null;
     1155                                                cb();
     1156                                        });
     1157
     1158                                        fileDrop.init();
     1159                                });
     1160                        });
     1161                }
     1162
     1163
     1164                o.inSeries(queue, function() {
     1165                        if (typeof(cb) === 'function') {
     1166                                cb(inited);
     1167                        }
     1168                });
     1169        }
     1170
     1171
     1172        function resizeImage(blob, params, cb) {
     1173                var img = new o.Image();
     1174
     1175                try {
     1176                        img.onload = function() {
     1177                                // no manipulation required if...
     1178                                if (params.width > this.width &&
     1179                                        params.height > this.height &&
     1180                                        params.quality === undef &&
     1181                                        params.preserve_headers &&
     1182                                        !params.crop
     1183                                ) {
     1184                                        this.destroy();
     1185                                        return cb(blob);
     1186                                }
     1187                                // otherwise downsize
     1188                                img.downsize(params.width, params.height, params.crop, params.preserve_headers);
     1189                        };
     1190
     1191                        img.onresize = function() {
     1192                                cb(this.getAsBlob(blob.type, params.quality));
     1193                                this.destroy();
     1194                        };
     1195
     1196                        img.onerror = function() {
     1197                                cb(blob);
     1198                        };
     1199
     1200                        img.load(blob);
     1201                } catch(ex) {
     1202                        cb(blob);
     1203                }
     1204        }
     1205
     1206
     1207        function setOption(option, value, init) {
     1208                var self = this, reinitRequired = false;
     1209
     1210                function _setOption(option, value, init) {
     1211                        var oldValue = settings[option];
     1212
     1213                        switch (option) {
     1214                                case 'max_file_size':
     1215                                        if (option === 'max_file_size') {
     1216                                                settings.max_file_size = settings.filters.max_file_size = value;
     1217                                        }
     1218                                        break;
     1219
     1220                                case 'chunk_size':
     1221                                        if (value = plupload.parseSize(value)) {
     1222                                                settings[option] = value;
     1223                                                settings.send_file_name = true;
     1224                                        }
     1225                                        break;
     1226
     1227                                case 'multipart':
     1228                                        settings[option] = value;
     1229                                        if (!value) {
     1230                                                settings.send_file_name = true;
     1231                                        }
     1232                                        break;
     1233
     1234                                case 'unique_names':
     1235                                        settings[option] = value;
     1236                                        if (value) {
     1237                                                settings.send_file_name = true;
     1238                                        }
     1239                                        break;
     1240
     1241                                case 'filters':
     1242                                        // for sake of backward compatibility
     1243                                        if (plupload.typeOf(value) === 'array') {
     1244                                                value = {
     1245                                                        mime_types: value
     1246                                                };
     1247                                        }
     1248
     1249                                        if (init) {
     1250                                                plupload.extend(settings.filters, value);
     1251                                        } else {
     1252                                                settings.filters = value;
     1253                                        }
     1254
     1255                                        // if file format filters are being updated, regenerate the matching expressions
     1256                                        if (value.mime_types) {
     1257                                                settings.filters.mime_types.regexp = (function(filters) {
     1258                                                        var extensionsRegExp = [];
     1259
     1260                                                        plupload.each(filters, function(filter) {
     1261                                                                plupload.each(filter.extensions.split(/,/), function(ext) {
     1262                                                                        if (/^\s*\*\s*$/.test(ext)) {
     1263                                                                                extensionsRegExp.push('\\.*');
     1264                                                                        } else {
     1265                                                                                extensionsRegExp.push('\\.' + ext.replace(new RegExp('[' + ('/^$.*+?|()[]{}\\'.replace(/./g, '\\$&')) + ']', 'g'), '\\$&'));
     1266                                                                        }
     1267                                                                });
     1268                                                        });
     1269
     1270                                                        return new RegExp('(' + extensionsRegExp.join('|') + ')$', 'i');
     1271                                                }(settings.filters.mime_types));
     1272                                        }
     1273                                        break;
     1274       
     1275                                case 'resize':
     1276                                        if (init) {
     1277                                                plupload.extend(settings.resize, value, {
     1278                                                        enabled: true
     1279                                                });
     1280                                        } else {
     1281                                                settings.resize = value;
     1282                                        }
     1283                                        break;
     1284
     1285                                case 'prevent_duplicates':
     1286                                        settings.prevent_duplicates = settings.filters.prevent_duplicates = !!value;
     1287                                        break;
     1288
     1289                                // options that require reinitialisation
     1290                                case 'container':
     1291                                case 'browse_button':
     1292                                case 'drop_element':
     1293                                                value = 'container' === option
     1294                                                        ? plupload.get(value)
     1295                                                        : plupload.getAll(value)
     1296                                                        ;
     1297                               
     1298                                case 'runtimes':
     1299                                case 'multi_selection':
     1300                                        settings[option] = value;
     1301                                        if (!init) {
     1302                                                reinitRequired = true;
     1303                                        }
     1304                                        break;
     1305
     1306                                default:
     1307                                        settings[option] = value;
     1308                        }
     1309
     1310                        if (!init) {
     1311                                self.trigger('OptionChanged', option, value, oldValue);
     1312                        }
     1313                }
     1314
     1315                if (typeof(option) === 'object') {
     1316                        plupload.each(option, function(value, option) {
     1317                                _setOption(option, value, init);
     1318                        });
     1319                } else {
     1320                        _setOption(option, value, init);
     1321                }
     1322
     1323                if (init) {
     1324                        // Normalize the list of required capabilities
     1325                        settings.required_features = normalizeCaps(plupload.extend({}, settings));
     1326
     1327                        // Come up with the list of capabilities that can affect default mode in a multi-mode runtimes
     1328                        preferred_caps = normalizeCaps(plupload.extend({}, settings, {
     1329                                required_features: true
     1330                        }));
     1331                } else if (reinitRequired) {
     1332                        self.trigger('Destroy');
     1333                       
     1334                        initControls.call(self, settings, function(inited) {
     1335                                if (inited) {
     1336                                        self.runtime = o.Runtime.getInfo(getRUID()).type;
     1337                                        self.trigger('Init', { runtime: self.runtime });
     1338                                        self.trigger('PostInit');
     1339                                } else {
     1340                                        self.trigger('Error', {
     1341                                                code : plupload.INIT_ERROR,
     1342                                                message : plupload.translate('Init error.')
     1343                                        });
     1344                                }
     1345                        });
     1346                }
     1347        }
     1348
     1349
     1350        // Internal event handlers
     1351        function onBeforeUpload(up, file) {
     1352                // Generate unique target filenames
     1353                if (up.settings.unique_names) {
     1354                        var matches = file.name.match(/\.([^.]+)$/), ext = "part";
     1355                        if (matches) {
     1356                                ext = matches[1];
     1357                        }
     1358                        file.target_name = file.id + '.' + ext;
     1359                }
     1360        }
     1361
     1362
     1363        function onUploadFile(up, file) {
     1364                var url = up.settings.url
     1365                , chunkSize = up.settings.chunk_size
     1366                , retries = up.settings.max_retries
     1367                , features = up.features
     1368                , offset = 0
     1369                , blob
     1370                ;
     1371
     1372                // make sure we start at a predictable offset
     1373                if (file.loaded) {
     1374                        offset = file.loaded = chunkSize ? chunkSize * Math.floor(file.loaded / chunkSize) : 0;
     1375                }
     1376
     1377                function handleError() {
     1378                        if (retries-- > 0) {
     1379                                delay(uploadNextChunk, 1000);
     1380                        } else {
     1381                                file.loaded = offset; // reset all progress
     1382
     1383                                up.trigger('Error', {
     1384                                        code : plupload.HTTP_ERROR,
     1385                                        message : plupload.translate('HTTP Error.'),
     1386                                        file : file,
     1387                                        response : xhr.responseText,
     1388                                        status : xhr.status,
     1389                                        responseHeaders: xhr.getAllResponseHeaders()
     1390                                });
     1391                        }
     1392                }
     1393
     1394                function uploadNextChunk() {
     1395                        var chunkBlob, formData, args = {}, curChunkSize;
     1396
     1397                        // make sure that file wasn't cancelled and upload is not stopped in general
     1398                        if (file.status !== plupload.UPLOADING || up.state === plupload.STOPPED) {
     1399                                return;
     1400                        }
     1401
     1402                        // send additional 'name' parameter only if required
     1403                        if (up.settings.send_file_name) {
     1404                                args.name = file.target_name || file.name;
     1405                        }
     1406
     1407                        if (chunkSize && features.chunks && blob.size > chunkSize) { // blob will be of type string if it was loaded in memory
     1408                                curChunkSize = Math.min(chunkSize, blob.size - offset);
     1409                                chunkBlob = blob.slice(offset, offset + curChunkSize);
     1410                        } else {
     1411                                curChunkSize = blob.size;
     1412                                chunkBlob = blob;
     1413                        }
     1414
     1415                        // If chunking is enabled add corresponding args, no matter if file is bigger than chunk or smaller
     1416                        if (chunkSize && features.chunks) {
     1417                                // Setup query string arguments
     1418                                if (up.settings.send_chunk_number) {
     1419                                        args.chunk = Math.ceil(offset / chunkSize);
     1420                                        args.chunks = Math.ceil(blob.size / chunkSize);
     1421                                } else { // keep support for experimental chunk format, just in case
     1422                                        args.offset = offset;
     1423                                        args.total = blob.size;
     1424                                }
     1425                        }
     1426
     1427                        xhr = new o.XMLHttpRequest();
     1428
     1429                        // Do we have upload progress support
     1430                        if (xhr.upload) {
     1431                                xhr.upload.onprogress = function(e) {
     1432                                        file.loaded = Math.min(file.size, offset + e.loaded);
     1433                                        up.trigger('UploadProgress', file);
     1434                                };
     1435                        }
     1436
     1437                        xhr.onload = function() {
     1438                                // check if upload made itself through
     1439                                if (xhr.status >= 400) {
     1440                                        handleError();
     1441                                        return;
     1442                                }
     1443
     1444                                retries = up.settings.max_retries; // reset the counter
     1445
     1446                                // Handle chunk response
     1447                                if (curChunkSize < blob.size) {
     1448                                        chunkBlob.destroy();
     1449
     1450                                        offset += curChunkSize;
     1451                                        file.loaded = Math.min(offset, blob.size);
     1452
     1453                                        up.trigger('ChunkUploaded', file, {
     1454                                                offset : file.loaded,
     1455                                                total : blob.size,
     1456                                                response : xhr.responseText,
     1457                                                status : xhr.status,
     1458                                                responseHeaders: xhr.getAllResponseHeaders()
     1459                                        });
     1460
     1461                                        // stock Android browser doesn't fire upload progress events, but in chunking mode we can fake them
     1462                                        if (o.Env.browser === 'Android Browser') {
     1463                                                // doesn't harm in general, but is not required anywhere else
     1464                                                up.trigger('UploadProgress', file);
     1465                                        }
     1466                                } else {
     1467                                        file.loaded = file.size;
     1468                                }
     1469
     1470                                chunkBlob = formData = null; // Free memory
     1471
     1472                                // Check if file is uploaded
     1473                                if (!offset || offset >= blob.size) {
     1474                                        // If file was modified, destory the copy
     1475                                        if (file.size != file.origSize) {
     1476                                                blob.destroy();
     1477                                                blob = null;
     1478                                        }
     1479
     1480                                        up.trigger('UploadProgress', file);
     1481
     1482                                        file.status = plupload.DONE;
     1483
     1484                                        up.trigger('FileUploaded', file, {
     1485                                                response : xhr.responseText,
     1486                                                status : xhr.status,
     1487                                                responseHeaders: xhr.getAllResponseHeaders()
     1488                                        });
     1489                                } else {
     1490                                        // Still chunks left
     1491                                        delay(uploadNextChunk, 1); // run detached, otherwise event handlers interfere
     1492                                }
     1493                        };
     1494
     1495                        xhr.onerror = function() {
     1496                                handleError();
     1497                        };
     1498
     1499                        xhr.onloadend = function() {
     1500                                this.destroy();
     1501                                xhr = null;
     1502                        };
     1503
     1504                        // Build multipart request
     1505                        if (up.settings.multipart && features.multipart) {
     1506                                xhr.open("post", url, true);
     1507
     1508                                // Set custom headers
     1509                                plupload.each(up.settings.headers, function(value, name) {
     1510                                        xhr.setRequestHeader(name, value);
     1511                                });
     1512
     1513                                formData = new o.FormData();
     1514
     1515                                // Add multipart params
     1516                                plupload.each(plupload.extend(args, up.settings.multipart_params), function(value, name) {
     1517                                        formData.append(name, value);
     1518                                });
     1519
     1520                                // Add file and send it
     1521                                formData.append(up.settings.file_data_name, chunkBlob);
     1522                                xhr.send(formData, {
     1523                                        runtime_order: up.settings.runtimes,
     1524                                        required_caps: up.settings.required_features,
     1525                                        preferred_caps: preferred_caps
     1526                                });
     1527                        } else {
     1528                                // if no multipart, send as binary stream
     1529                                url = plupload.buildUrl(up.settings.url, plupload.extend(args, up.settings.multipart_params));
     1530
     1531                                xhr.open("post", url, true);
     1532
     1533                                xhr.setRequestHeader('Content-Type', 'application/octet-stream'); // Binary stream header
     1534
     1535                                // Set custom headers
     1536                                plupload.each(up.settings.headers, function(value, name) {
     1537                                        xhr.setRequestHeader(name, value);
     1538                                });
     1539
     1540                                xhr.send(chunkBlob, {
     1541                                        runtime_order: up.settings.runtimes,
     1542                                        required_caps: up.settings.required_features,
     1543                                        preferred_caps: preferred_caps
     1544                                });
     1545                        }
     1546                }
     1547
     1548                blob = file.getSource();
     1549
     1550                // Start uploading chunks
     1551                if (up.settings.resize.enabled && runtimeCan(blob, 'send_binary_string') && !!~o.inArray(blob.type, ['image/jpeg', 'image/png'])) {
     1552                        // Resize if required
     1553                        resizeImage.call(this, blob, up.settings.resize, function(resizedBlob) {
     1554                                blob = resizedBlob;
     1555                                file.size = resizedBlob.size;
     1556                                uploadNextChunk();
     1557                        });
     1558                } else {
     1559                        uploadNextChunk();
     1560                }
     1561        }
     1562
     1563
     1564        function onUploadProgress(up, file) {
     1565                calcFile(file);
     1566        }
     1567
     1568
     1569        function onStateChanged(up) {
     1570                if (up.state == plupload.STARTED) {
     1571                        // Get start time to calculate bps
     1572                        startTime = (+new Date());
     1573                } else if (up.state == plupload.STOPPED) {
     1574                        // Reset currently uploading files
     1575                        for (var i = up.files.length - 1; i >= 0; i--) {
     1576                                if (up.files[i].status == plupload.UPLOADING) {
     1577                                        up.files[i].status = plupload.QUEUED;
     1578                                        calc();
     1579                                }
     1580                        }
     1581                }
     1582        }
     1583
     1584
     1585        function onCancelUpload() {
     1586                if (xhr) {
     1587                        xhr.abort();
     1588                }
     1589        }
     1590
     1591
     1592        function onFileUploaded(up) {
     1593                calc();
     1594
     1595                // Upload next file but detach it from the error event
     1596                // since other custom listeners might want to stop the queue
     1597                delay(function() {
     1598                        uploadNext.call(up);
     1599                }, 1);
     1600        }
     1601
     1602
     1603        function onError(up, err) {
     1604                if (err.code === plupload.INIT_ERROR) {
     1605                        up.destroy();
     1606                }
     1607                // Set failed status if an error occured on a file
     1608                else if (err.code === plupload.HTTP_ERROR) {
     1609                        err.file.status = plupload.FAILED;
     1610                        calcFile(err.file);
     1611
     1612                        // Upload next file but detach it from the error event
     1613                        // since other custom listeners might want to stop the queue
     1614                        if (up.state == plupload.STARTED) { // upload in progress
     1615                                up.trigger('CancelUpload');
     1616                                delay(function() {
     1617                                        uploadNext.call(up);
     1618                                }, 1);
     1619                        }
     1620                }
     1621        }
     1622
     1623
     1624        function onDestroy(up) {
     1625                up.stop();
     1626
     1627                // Purge the queue
     1628                plupload.each(files, function(file) {
     1629                        file.destroy();
     1630                });
     1631                files = [];
     1632
     1633                if (fileInputs.length) {
     1634                        plupload.each(fileInputs, function(fileInput) {
     1635                                fileInput.destroy();
     1636                        });
     1637                        fileInputs = [];
     1638                }
     1639
     1640                if (fileDrops.length) {
     1641                        plupload.each(fileDrops, function(fileDrop) {
     1642                                fileDrop.destroy();
     1643                        });
     1644                        fileDrops = [];
     1645                }
     1646
     1647                preferred_caps = {};
     1648                disabled = false;
     1649                startTime = xhr = null;
     1650                total.reset();
     1651        }
     1652
     1653
     1654        // Default settings
     1655        settings = {
     1656                runtimes: o.Runtime.order,
     1657                max_retries: 0,
     1658                chunk_size: 0,
     1659                multipart: true,
     1660                multi_selection: true,
     1661                file_data_name: 'file',
     1662                filters: {
     1663                        mime_types: [],
     1664                        prevent_duplicates: false,
     1665                        max_file_size: 0
     1666                },
     1667                resize: {
     1668                        enabled: false,
     1669                        preserve_headers: true,
     1670                        crop: false
     1671                },
     1672                send_file_name: true,
     1673                send_chunk_number: true
     1674        };
     1675
     1676       
     1677        setOption.call(this, options, null, true);
     1678
     1679        // Inital total state
     1680        total = new plupload.QueueProgress();
     1681
     1682        // Add public methods
     1683        plupload.extend(this, {
     1684
     1685                /**
     1686                 * Unique id for the Uploader instance.
     1687                 *
     1688                 * @property id
     1689                 * @type String
     1690                 */
     1691                id : uid,
     1692                uid : uid, // mOxie uses this to differentiate between event targets
     1693
     1694                /**
     1695                 * Current state of the total uploading progress. This one can either be plupload.STARTED or plupload.STOPPED.
     1696                 * These states are controlled by the stop/start methods. The default value is STOPPED.
     1697                 *
     1698                 * @property state
     1699                 * @type Number
     1700                 */
     1701                state : plupload.STOPPED,
     1702
     1703                /**
     1704                 * Map of features that are available for the uploader runtime. Features will be filled
     1705                 * before the init event is called, these features can then be used to alter the UI for the end user.
     1706                 * Some of the current features that might be in this map is: dragdrop, chunks, jpgresize, pngresize.
     1707                 *
     1708                 * @property features
     1709                 * @type Object
     1710                 */
     1711                features : {},
     1712
     1713                /**
     1714                 * Current runtime name.
     1715                 *
     1716                 * @property runtime
     1717                 * @type String
     1718                 */
     1719                runtime : null,
     1720
     1721                /**
     1722                 * Current upload queue, an array of File instances.
     1723                 *
     1724                 * @property files
     1725                 * @type Array
     1726                 * @see plupload.File
     1727                 */
     1728                files : files,
     1729
     1730                /**
     1731                 * Object with name/value settings.
     1732                 *
     1733                 * @property settings
     1734                 * @type Object
     1735                 */
     1736                settings : settings,
     1737
     1738                /**
     1739                 * Total progess information. How many files has been uploaded, total percent etc.
     1740                 *
     1741                 * @property total
     1742                 * @type plupload.QueueProgress
     1743                 */
     1744                total : total,
     1745
     1746
     1747                /**
     1748                 * Initializes the Uploader instance and adds internal event listeners.
     1749                 *
     1750                 * @method init
     1751                 */
     1752                init : function() {
     1753                        var self = this, opt, preinitOpt, err;
     1754                       
     1755                        preinitOpt = self.getOption('preinit');
     1756                        if (typeof(preinitOpt) == "function") {
     1757                                preinitOpt(self);
     1758                        } else {
     1759                                plupload.each(preinitOpt, function(func, name) {
     1760                                        self.bind(name, func);
     1761                                });
     1762                        }
     1763
     1764                        bindEventListeners.call(self);
     1765
     1766                        // Check for required options
     1767                        plupload.each(['container', 'browse_button', 'drop_element'], function(el) {
     1768                                if (self.getOption(el) === null) {
     1769                                        err = {
     1770                                                code : plupload.INIT_ERROR,
     1771                                                message : plupload.translate("'%' specified, but cannot be found.")
     1772                                        }
     1773                                        return false;
     1774                                }
     1775                        });
     1776
     1777                        if (err) {
     1778                                return self.trigger('Error', err);
     1779                        }
     1780
     1781
     1782                        if (!settings.browse_button && !settings.drop_element) {
     1783                                return self.trigger('Error', {
     1784                                        code : plupload.INIT_ERROR,
     1785                                        message : plupload.translate("You must specify either 'browse_button' or 'drop_element'.")
     1786                                });
     1787                        }
     1788
     1789
     1790                        initControls.call(self, settings, function(inited) {
     1791                                var initOpt = self.getOption('init');
     1792                                if (typeof(initOpt) == "function") {
     1793                                        initOpt(self);
     1794                                } else {
     1795                                        plupload.each(initOpt, function(func, name) {
     1796                                                self.bind(name, func);
     1797                                        });
     1798                                }
     1799
     1800                                if (inited) {
     1801                                        self.runtime = o.Runtime.getInfo(getRUID()).type;
     1802                                        self.trigger('Init', { runtime: self.runtime });
     1803                                        self.trigger('PostInit');
     1804                                } else {
     1805                                        self.trigger('Error', {
     1806                                                code : plupload.INIT_ERROR,
     1807                                                message : plupload.translate('Init error.')
     1808                                        });
     1809                                }
     1810                        });
     1811                },
     1812
     1813                /**
     1814                 * Set the value for the specified option(s).
     1815                 *
     1816                 * @method setOption
     1817                 * @since 2.1
     1818                 * @param {String|Object} option Name of the option to change or the set of key/value pairs
     1819                 * @param {Mixed} [value] Value for the option (is ignored, if first argument is object)
     1820                 */
     1821                setOption: function(option, value) {
     1822                        setOption.call(this, option, value, !this.runtime); // until runtime not set we do not need to reinitialize
     1823                },
     1824
     1825                /**
     1826                 * Get the value for the specified option or the whole configuration, if not specified.
     1827                 *
     1828                 * @method getOption
     1829                 * @since 2.1
     1830                 * @param {String} [option] Name of the option to get
     1831                 * @return {Mixed} Value for the option or the whole set
     1832                 */
     1833                getOption: function(option) {
     1834                        if (!option) {
     1835                                return settings;
     1836                        }
     1837                        return settings[option];
     1838                },
     1839
     1840                /**
     1841                 * Refreshes the upload instance by dispatching out a refresh event to all runtimes.
     1842                 * This would for example reposition flash/silverlight shims on the page.
     1843                 *
     1844                 * @method refresh
     1845                 */
     1846                refresh : function() {
     1847                        if (fileInputs.length) {
     1848                                plupload.each(fileInputs, function(fileInput) {
     1849                                        fileInput.trigger('Refresh');
     1850                                });
     1851                        }
     1852                        this.trigger('Refresh');
     1853                },
     1854
     1855                /**
     1856                 * Starts uploading the queued files.
     1857                 *
     1858                 * @method start
     1859                 */
     1860                start : function() {
     1861                        if (this.state != plupload.STARTED) {
     1862                                this.state = plupload.STARTED;
     1863                                this.trigger('StateChanged');
     1864
     1865                                uploadNext.call(this);
     1866                        }
     1867                },
     1868
     1869                /**
     1870                 * Stops the upload of the queued files.
     1871                 *
     1872                 * @method stop
     1873                 */
     1874                stop : function() {
     1875                        if (this.state != plupload.STOPPED) {
     1876                                this.state = plupload.STOPPED;
     1877                                this.trigger('StateChanged');
     1878                                this.trigger('CancelUpload');
     1879                        }
     1880                },
     1881
     1882
     1883                /**
     1884                 * Disables/enables browse button on request.
     1885                 *
     1886                 * @method disableBrowse
     1887                 * @param {Boolean} disable Whether to disable or enable (default: true)
     1888                 */
     1889                disableBrowse : function() {
     1890                        disabled = arguments[0] !== undef ? arguments[0] : true;
     1891
     1892                        if (fileInputs.length) {
     1893                                plupload.each(fileInputs, function(fileInput) {
     1894                                        fileInput.disable(disabled);
     1895                                });
     1896                        }
     1897
     1898                        this.trigger('DisableBrowse', disabled);
     1899                },
     1900
     1901                /**
     1902                 * Returns the specified file object by id.
     1903                 *
     1904                 * @method getFile
     1905                 * @param {String} id File id to look for.
     1906                 * @return {plupload.File} File object or undefined if it wasn't found;
     1907                 */
     1908                getFile : function(id) {
     1909                        var i;
     1910                        for (i = files.length - 1; i >= 0; i--) {
     1911                                if (files[i].id === id) {
     1912                                        return files[i];
     1913                                }
     1914                        }
     1915                },
     1916
     1917                /**
     1918                 * Adds file to the queue programmatically. Can be native file, instance of Plupload.File,
     1919                 * instance of mOxie.File, input[type="file"] element, or array of these. Fires FilesAdded,
     1920                 * if any files were added to the queue. Otherwise nothing happens.
     1921                 *
     1922                 * @method addFile
     1923                 * @since 2.0
     1924                 * @param {plupload.File|mOxie.File|File|Node|Array} file File or files to add to the queue.
     1925                 * @param {String} [fileName] If specified, will be used as a name for the file
     1926                 */
     1927                addFile : function(file, fileName) {
     1928                        var self = this
     1929                        , queue = []
     1930                        , filesAdded = []
     1931                        , ruid
     1932                        ;
     1933
     1934                        function filterFile(file, cb) {
     1935                                var queue = [];
     1936                                o.each(self.settings.filters, function(rule, name) {
     1937                                        if (fileFilters[name]) {
     1938                                                queue.push(function(cb) {
     1939                                                        fileFilters[name].call(self, rule, file, function(res) {
     1940                                                                cb(!res);
     1941                                                        });
     1942                                                });
     1943                                        }
     1944                                });
     1945                                o.inSeries(queue, cb);
     1946                        }
     1947
     1948                        /**
     1949                         * @method resolveFile
     1950                         * @private
     1951                         * @param {o.File|o.Blob|plupload.File|File|Blob|input[type="file"]} file
     1952                         */
     1953                        function resolveFile(file) {
     1954                                var type = o.typeOf(file);
     1955
     1956                                // o.File
     1957                                if (file instanceof o.File) {
     1958                                        if (!file.ruid && !file.isDetached()) {
     1959                                                if (!ruid) { // weird case
     1960                                                        return false;
     1961                                                }
     1962                                                file.ruid = ruid;
     1963                                                file.connectRuntime(ruid);
     1964                                        }
     1965                                        resolveFile(new plupload.File(file));
     1966                                }
     1967                                // o.Blob
     1968                                else if (file instanceof o.Blob) {
     1969                                        resolveFile(file.getSource());
     1970                                        file.destroy();
     1971                                }
     1972                                // plupload.File - final step for other branches
     1973                                else if (file instanceof plupload.File) {
     1974                                        if (fileName) {
     1975                                                file.name = fileName;
     1976                                        }
     1977                                       
     1978                                        queue.push(function(cb) {
     1979                                                // run through the internal and user-defined filters, if any
     1980                                                filterFile(file, function(err) {
     1981                                                        if (!err) {
     1982                                                                // make files available for the filters by updating the main queue directly
     1983                                                                files.push(file);
     1984                                                                // collect the files that will be passed to FilesAdded event
     1985                                                                filesAdded.push(file);
     1986
     1987                                                                self.trigger("FileFiltered", file);
     1988                                                        }
     1989                                                        delay(cb, 1); // do not build up recursions or eventually we might hit the limits
     1990                                                });
     1991                                        });
     1992                                }
     1993                                // native File or blob
     1994                                else if (o.inArray(type, ['file', 'blob']) !== -1) {
     1995                                        resolveFile(new o.File(null, file));
     1996                                }
     1997                                // input[type="file"]
     1998                                else if (type === 'node' && o.typeOf(file.files) === 'filelist') {
     1999                                        // if we are dealing with input[type="file"]
     2000                                        o.each(file.files, resolveFile);
     2001                                }
     2002                                // mixed array of any supported types (see above)
     2003                                else if (type === 'array') {
     2004                                        fileName = null; // should never happen, but unset anyway to avoid funny situations
     2005                                        o.each(file, resolveFile);
     2006                                }
     2007                        }
     2008
     2009                        ruid = getRUID();
     2010                       
     2011                        resolveFile(file);
     2012
     2013                        if (queue.length) {
     2014                                o.inSeries(queue, function() {
     2015                                        // if any files left after filtration, trigger FilesAdded
     2016                                        if (filesAdded.length) {
     2017                                                self.trigger("FilesAdded", filesAdded);
     2018                                        }
     2019                                });
     2020                        }
     2021                },
     2022
     2023                /**
     2024                 * Removes a specific file.
     2025                 *
     2026                 * @method removeFile
     2027                 * @param {plupload.File|String} file File to remove from queue.
     2028                 */
     2029                removeFile : function(file) {
     2030                        var id = typeof(file) === 'string' ? file : file.id;
     2031
     2032                        for (var i = files.length - 1; i >= 0; i--) {
     2033                                if (files[i].id === id) {
     2034                                        return this.splice(i, 1)[0];
     2035                                }
     2036                        }
     2037                },
     2038
     2039                /**
     2040                 * Removes part of the queue and returns the files removed. This will also trigger the FilesRemoved and QueueChanged events.
     2041                 *
     2042                 * @method splice
     2043                 * @param {Number} start (Optional) Start index to remove from.
     2044                 * @param {Number} length (Optional) Lengh of items to remove.
     2045                 * @return {Array} Array of files that was removed.
     2046                 */
     2047                splice : function(start, length) {
     2048                        // Splice and trigger events
     2049                        var removed = files.splice(start === undef ? 0 : start, length === undef ? files.length : length);
     2050
     2051                        // if upload is in progress we need to stop it and restart after files are removed
     2052                        var restartRequired = false;
     2053                        if (this.state == plupload.STARTED) { // upload in progress
     2054                                plupload.each(removed, function(file) {
     2055                                        if (file.status === plupload.UPLOADING) {
     2056                                                restartRequired = true; // do not restart, unless file that is being removed is uploading
     2057                                                return false;
     2058                                        }
     2059                                });
     2060                               
     2061                                if (restartRequired) {
     2062                                        this.stop();
     2063                                }
     2064                        }
     2065
     2066                        this.trigger("FilesRemoved", removed);
     2067
     2068                        // Dispose any resources allocated by those files
     2069                        plupload.each(removed, function(file) {
     2070                                file.destroy();
     2071                        });
     2072                       
     2073                        if (restartRequired) {
     2074                                this.start();
     2075                        }
     2076
     2077                        return removed;
     2078                },
     2079
     2080                /**
     2081                Dispatches the specified event name and its arguments to all listeners.
     2082
     2083                @method trigger
     2084                @param {String} name Event name to fire.
     2085                @param {Object..} Multiple arguments to pass along to the listener functions.
     2086                */
     2087
     2088                // override the parent method to match Plupload-like event logic
     2089                dispatchEvent: function(type) {
     2090                        var list, args, result;
     2091                                               
     2092                        type = type.toLowerCase();
     2093                                                       
     2094                        list = this.hasEventListener(type);
     2095
     2096                        if (list) {
     2097                                // sort event list by priority
     2098                                list.sort(function(a, b) { return b.priority - a.priority; });
     2099                               
     2100                                // first argument should be current plupload.Uploader instance
     2101                                args = [].slice.call(arguments);
     2102                                args.shift();
     2103                                args.unshift(this);
     2104
     2105                                for (var i = 0; i < list.length; i++) {
     2106                                        // Fire event, break chain if false is returned
     2107                                        if (list[i].fn.apply(list[i].scope, args) === false) {
     2108                                                return false;
     2109                                        }
     2110                                }
     2111                        }
     2112                        return true;
     2113                },
     2114
     2115                /**
     2116                Check whether uploader has any listeners to the specified event.
     2117
     2118                @method hasEventListener
     2119                @param {String} name Event name to check for.
     2120                */
     2121
     2122
     2123                /**
     2124                Adds an event listener by name.
     2125
     2126                @method bind
     2127                @param {String} name Event name to listen for.
     2128                @param {function} fn Function to call ones the event gets fired.
     2129                @param {Object} [scope] Optional scope to execute the specified function in.
     2130                @param {Number} [priority=0] Priority of the event handler - handlers with higher priorities will be called first
     2131                */
     2132                bind: function(name, fn, scope, priority) {
     2133                        // adapt moxie EventTarget style to Plupload-like
     2134                        plupload.Uploader.prototype.bind.call(this, name, fn, priority, scope);
     2135                },
     2136
     2137                /**
     2138                Removes the specified event listener.
     2139
     2140                @method unbind
     2141                @param {String} name Name of event to remove.
     2142                @param {function} fn Function to remove from listener.
     2143                */
     2144
     2145                /**
     2146                Removes all event listeners.
     2147
     2148                @method unbindAll
     2149                */
     2150
     2151
     2152                /**
     2153                 * Destroys Plupload instance and cleans after itself.
     2154                 *
     2155                 * @method destroy
     2156                 */
     2157                destroy : function() {
     2158                        this.trigger('Destroy');
     2159                        settings = total = null; // purge these exclusively
     2160                        this.unbindAll();
     2161                }
     2162        });
     2163};
     2164
     2165plupload.Uploader.prototype = o.EventTarget.instance;
     2166
     2167/**
     2168 * Constructs a new file instance.
     2169 *
     2170 * @class File
     2171 * @constructor
     2172 *
     2173 * @param {Object} file Object containing file properties
     2174 * @param {String} file.name Name of the file.
     2175 * @param {Number} file.size File size.
     2176 */
     2177plupload.File = (function() {
     2178        var filepool = {};
     2179
     2180        function PluploadFile(file) {
     2181
     2182                plupload.extend(this, {
     2183
     2184                        /**
     2185                         * File id this is a globally unique id for the specific file.
     2186                         *
     2187                         * @property id
     2188                         * @type String
     2189                         */
     2190                        id: plupload.guid(),
     2191
     2192                        /**
     2193                         * File name for example "myfile.gif".
     2194                         *
     2195                         * @property name
     2196                         * @type String
     2197                         */
     2198                        name: file.name || file.fileName,
     2199
     2200                        /**
     2201                         * File type, `e.g image/jpeg`
     2202                         *
     2203                         * @property type
     2204                         * @type String
     2205                         */
     2206                        type: file.type || '',
     2207
     2208                        /**
     2209                         * File size in bytes (may change after client-side manupilation).
     2210                         *
     2211                         * @property size
     2212                         * @type Number
     2213                         */
     2214                        size: file.size || file.fileSize,
     2215
     2216                        /**
     2217                         * Original file size in bytes.
     2218                         *
     2219                         * @property origSize
     2220                         * @type Number
     2221                         */
     2222                        origSize: file.size || file.fileSize,
     2223
     2224                        /**
     2225                         * Number of bytes uploaded of the files total size.
     2226                         *
     2227                         * @property loaded
     2228                         * @type Number
     2229                         */
     2230                        loaded: 0,
     2231
     2232                        /**
     2233                         * Number of percentage uploaded of the file.
     2234                         *
     2235                         * @property percent
     2236                         * @type Number
     2237                         */
     2238                        percent: 0,
     2239
     2240                        /**
     2241                         * Status constant matching the plupload states QUEUED, UPLOADING, FAILED, DONE.
     2242                         *
     2243                         * @property status
     2244                         * @type Number
     2245                         * @see plupload
     2246                         */
     2247                        status: plupload.QUEUED,
     2248
     2249                        /**
     2250                         * Date of last modification.
     2251                         *
     2252                         * @property lastModifiedDate
     2253                         * @type {String}
     2254                         */
     2255                        lastModifiedDate: file.lastModifiedDate || (new Date()).toLocaleString(), // Thu Aug 23 2012 19:40:00 GMT+0400 (GET)
     2256
     2257                        /**
     2258                         * Returns native window.File object, when it's available.
     2259                         *
     2260                         * @method getNative
     2261                         * @return {window.File} or null, if plupload.File is of different origin
     2262                         */
     2263                        getNative: function() {
     2264                                var file = this.getSource().getSource();
     2265                                return o.inArray(o.typeOf(file), ['blob', 'file']) !== -1 ? file : null;
     2266                        },
     2267
     2268                        /**
     2269                         * Returns mOxie.File - unified wrapper object that can be used across runtimes.
     2270                         *
     2271                         * @method getSource
     2272                         * @return {mOxie.File} or null
     2273                         */
     2274                        getSource: function() {
     2275                                if (!filepool[this.id]) {
     2276                                        return null;
     2277                                }
     2278                                return filepool[this.id];
     2279                        },
     2280
     2281                        /**
     2282                         * Destroys plupload.File object.
     2283                         *
     2284                         * @method destroy
     2285                         */
     2286                        destroy: function() {
     2287                                var src = this.getSource();
     2288                                if (src) {
     2289                                        src.destroy();
     2290                                        delete filepool[this.id];
     2291                                }
     2292                        }
     2293                });
     2294
     2295                filepool[this.id] = file;
     2296        }
     2297
     2298        return PluploadFile;
     2299}());
     2300
     2301
     2302/**
     2303 * Constructs a queue progress.
     2304 *
     2305 * @class QueueProgress
     2306 * @constructor
     2307 */
     2308 plupload.QueueProgress = function() {
     2309        var self = this; // Setup alias for self to reduce code size when it's compressed
     2310
     2311        /**
     2312         * Total queue file size.
     2313         *
     2314         * @property size
     2315         * @type Number
     2316         */
     2317        self.size = 0;
     2318
     2319        /**
     2320         * Total bytes uploaded.
     2321         *
     2322         * @property loaded
     2323         * @type Number
     2324         */
     2325        self.loaded = 0;
     2326
     2327        /**
     2328         * Number of files uploaded.
     2329         *
     2330         * @property uploaded
     2331         * @type Number
     2332         */
     2333        self.uploaded = 0;
     2334
     2335        /**
     2336         * Number of files failed to upload.
     2337         *
     2338         * @property failed
     2339         * @type Number
     2340         */
     2341        self.failed = 0;
     2342
     2343        /**
     2344         * Number of files yet to be uploaded.
     2345         *
     2346         * @property queued
     2347         * @type Number
     2348         */
     2349        self.queued = 0;
     2350
     2351        /**
     2352         * Total percent of the uploaded bytes.
     2353         *
     2354         * @property percent
     2355         * @type Number
     2356         */
     2357        self.percent = 0;
     2358
     2359        /**
     2360         * Bytes uploaded per second.
     2361         *
     2362         * @property bytesPerSec
     2363         * @type Number
     2364         */
     2365        self.bytesPerSec = 0;
     2366
     2367        /**
     2368         * Resets the progress to its initial values.
     2369         *
     2370         * @method reset
     2371         */
     2372        self.reset = function() {
     2373                self.size = self.loaded = self.uploaded = self.failed = self.queued = self.percent = self.bytesPerSec = 0;
     2374        };
     2375};
     2376
     2377window.plupload = plupload;
     2378
     2379}(window, mOxie));
  • plupload/wp-plupload.js

    diff --git a/src/js/_enqueues/wp/plupload/wp-plupload.js b/src/js/_enqueues/vendor/plupload/wp-plupload.js
    similarity index 98%
    rename from src/js/_enqueues/wp/plupload/wp-plupload.js
    rename to src/js/_enqueues/vendor/plupload/wp-plupload.js
    index be1827d572..ea32444c7e 100644
    old new  
    55 */
    66window.wp = window.wp || {};
    77
    8 // Suppress warning about Uploader function's unused "isIE" argument:
    9 /* jshint unused:false */
    108( function( exports, $ ) {
    119        var Uploader;
    1210
    window.wp = window.wp || {}; 
    145143                                // Do a cleanup then tell the user to scale down the image and upload it again.
    146144                                $.ajax({
    147145                                        type: 'post',
    148                                         url: ajaxurl, // jshint ignore:line
     146                                        url: ajaxurl,
    149147                                        dataType: 'json',
    150148                                        data: {
    151149                                                action: 'media-create-image-subsizes',
    152150                                                _wpnonce: _wpPluploadSettings.defaults.multipart_params._wpnonce,
    153151                                                attachment_id: id,
    154                                                 _wp_upload_failed_cleanup: true
     152                                                _wp_upload_failed_cleanup: true,
    155153                                        }
    156154                                });
    157155
    window.wp = window.wp || {}; 
    168166                        // Another request to try to create the missing image sub-sizes.
    169167                        $.ajax({
    170168                                type: 'post',
    171                                 url: ajaxurl, // jshint ignore:line
     169                                url: ajaxurl,
    172170                                dataType: 'json',
    173171                                data: {
    174172                                        action: 'media-create-image-subsizes',
    175173                                        _wpnonce: _wpPluploadSettings.defaults.multipart_params._wpnonce,
    176                                         attachment_id: id
     174                                        attachment_id: id,
    177175                                }
    178176                        }).done( function( response ) {
    179177                                if ( response.success ) {
    window.wp = window.wp || {}; 
    194192
    195193                                error( message, data, file, 'no-retry' );
    196194                        });
    197                 };
     195                }
    198196
    199197                /**
    200198                 * Custom error callback.
    window.wp = window.wp || {}; 
    258256                        }
    259257
    260258                        self.success( file.attachment );
    261                 };
     259                }
    262260
    263261                /**
    264262                 * After the Uploader has been initialized, initialize some behaviors for the dropzone.
    window.wp = window.wp || {}; 
    462460                        }
    463461
    464462                        return pluploadL10n.http_error;
    465                 }
     463                },
    466464        };
    467465
    468466        $.extend( Uploader.prototype, /** @lends wp.Uploader.prototype */{
  • src/wp-includes/script-loader.php

    diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php
    index 9c2c2dc867..9c505cf494 100644
    a b function wp_default_scripts( &$scripts ) { 
    12521252                'error_uploading'           => __( '&#8220;%s&#8221; has failed to upload.' ),
    12531253        );
    12541254
    1255         $scripts->add( 'moxiejs', "/wp-includes/js/plupload/moxie$suffix.js", array(), '1.5.7' );
    1256         $scripts->add( 'plupload', "/wp-includes/js/plupload/plupload$suffix.js", array( 'moxiejs' ), '2.3.6' );
     1255        $scripts->add( 'moxiejs', "/wp-includes/js/plupload/moxie$suffix.js", array(), '1.3.5' );
     1256        $scripts->add( 'plupload', "/wp-includes/js/plupload/plupload$suffix.js", array( 'moxiejs' ), '2.1.9' );
    12571257        // Back compat handles:
    12581258        foreach ( array( 'all', 'html5', 'flash', 'silverlight', 'html4' ) as $handle ) {
    1259                 $scripts->add( "plupload-$handle", false, array( 'plupload' ), '2.3.6' );
     1259                $scripts->add( "plupload-$handle", false, array( 'plupload' ), '2.1.1' );
    12601260        }
    12611261
    12621262        $scripts->add( 'plupload-handlers', "/wp-includes/js/plupload/handlers$suffix.js", array( 'plupload', 'jquery' ) );
  • new file tests/phpunit/tests/query/wpLoop.php

    -- 
    2.20.1
    
    From 6f42b3651c883ea7fd07c2c4dc4de680c8984b00 Mon Sep 17 00:00:00 2001
    From: Micah Wood <micah@wpscholar.com>
    Date: Sun, 9 Feb 2020 15:00:57 -0700
    Subject: [PATCH] Added unit tests for the wp_loop() function
    
    ---
     tests/phpunit/tests/query/wpLoop.php | 132 +++++++++++++++++++++++++++
     1 file changed, 132 insertions(+)
     create mode 100644 tests/phpunit/tests/query/wpLoop.php
    
    diff --git a/tests/phpunit/tests/query/wpLoop.php b/tests/phpunit/tests/query/wpLoop.php
    new file mode 100644
    index 0000000000..f6a587f929
    - +  
     1<?php
     2
     3/**
     4 * Tests to make sure looping through posts using the wp_loop() function works properly.
     5 *
     6 * @ticket 48193
     7 *
     8 * @group  query
     9 */
     10class Tests_WP_Loop extends WP_UnitTestCase {
     11
     12        /**
     13         * Create test posts in database.
     14         *
     15         * @param WP_UnitTest_Factory $factory
     16         */
     17        public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
     18                $factory->post->create( [ 'post_title' => 'Post 1' ] );
     19                $factory->post->create( [ 'post_title' => 'Post 2' ] );
     20                $factory->post->create( [ 'post_title' => 'Post 3' ] );
     21        }
     22
     23        /**
     24         * Test iterating over the global WP_Query instance.
     25         */
     26        public function test_global_query() {
     27
     28                // Ensure global query returns results in order by title.
     29                add_filter( 'pre_get_posts', function ( WP_Query $query ) {
     30                        if ( $query->is_main_query() ) {
     31                                $query->set( 'orderby', 'title' );
     32                                $query->set( 'order', 'ASC' );
     33                        }
     34                } );
     35
     36                // Required to setup global WP_Query instance
     37                $this->go_to( '/' );
     38
     39                $i = 0;
     40                foreach ( wp_loop() as $post ) {
     41                        $i ++;
     42                        $this->assertTrue( is_a( $post, 'WP_Post' ) );
     43                        $this->assertEquals( "Post {$i}", $post->post_title );
     44                        $this->assertEquals( "Post {$i}", get_the_title() );
     45                }
     46
     47        }
     48
     49        /**
     50         * Test iterating over a custom WP_Query instance.
     51         */
     52        public function test_custom_query() {
     53
     54                // Create query
     55                $query = new WP_Query( [
     56                        'post_type' => 'post',
     57                        'order_by'  => 'title',
     58                        'order'     => 'ASC',
     59                ] );
     60
     61                $i = 0;
     62                foreach ( wp_loop( $query ) as $post ) {
     63                        $i ++;
     64                        $this->assertTrue( is_a( $post, 'WP_Post' ) );
     65                        $this->assertEquals( "Post {$i}", $post->post_title );
     66                        $this->assertEquals( "Post {$i}", get_the_title() );
     67                }
     68        }
     69
     70        /**
     71         * Test iterating over an array of WP_Post objects.
     72         */
     73        public function test_array_of_posts() {
     74
     75                $posts = [
     76                        get_page_by_title( 'Post 1', OBJECT, 'post' ),
     77                        get_page_by_title( 'Post 2', OBJECT, 'post' ),
     78                        get_page_by_title( 'Post 3', OBJECT, 'post' ),
     79                ];
     80
     81                $i = 0;
     82                foreach ( wp_loop( $posts ) as $post ) {
     83                        $i ++;
     84                        $this->assertTrue( is_a( $post, 'WP_Post' ) );
     85                        $this->assertEquals( "Post {$i}", $post->post_title );
     86                        $this->assertEquals( "Post {$i}", get_the_title() );
     87                }
     88        }
     89
     90        /**
     91         * Test iterating over an array of post IDs.
     92         */
     93        public function test_array_of_post_ids() {
     94
     95                $post_ids = [
     96                        get_page_by_title( 'Post 1', OBJECT, 'post' )->ID,
     97                        get_page_by_title( 'Post 2', OBJECT, 'post' )->ID,
     98                        get_page_by_title( 'Post 3', OBJECT, 'post' )->ID,
     99                ];
     100
     101                $i = 0;
     102                foreach ( wp_loop( $post_ids ) as $post ) {
     103                        $i ++;
     104                        $this->assertTrue( is_a( $post, 'WP_Post' ) );
     105                        $this->assertEquals( "Post {$i}", $post->post_title );
     106                        $this->assertEquals( "Post {$i}", get_the_title() );
     107                }
     108        }
     109
     110        /**
     111         * Test iterating over an iterator.
     112         */
     113        public function test_looping_over_iterator() {
     114
     115                $posts = [
     116                        get_page_by_title( 'Post 1', OBJECT, 'post' ),
     117                        get_page_by_title( 'Post 2', OBJECT, 'post' ),
     118                        get_page_by_title( 'Post 3', OBJECT, 'post' ),
     119                ];
     120
     121                $iterator = new ArrayIterator( $posts );
     122
     123                $i = 0;
     124                foreach ( wp_loop( $iterator ) as $post ) {
     125                        $i ++;
     126                        $this->assertTrue( is_a( $post, 'WP_Post' ) );
     127                        $this->assertEquals( "Post {$i}", $post->post_title );
     128                        $this->assertEquals( "Post {$i}", get_the_title() );
     129                }
     130        }
     131
     132}
  • src/wp-includes/query.php

    -- 
    2.20.1
    
    From 16499f7d4c8268882f22d8b061428a394901dd8a Mon Sep 17 00:00:00 2001
    From: Micah Wood <micah@wpscholar.com>
    Date: Sun, 9 Feb 2020 15:01:10 -0700
    Subject: [PATCH] Updates to the wp_loop() function
    
    ---
     src/wp-includes/query.php | 27 +++++++++++++++------------
     1 file changed, 15 insertions(+), 12 deletions(-)
    
    diff --git a/src/wp-includes/query.php b/src/wp-includes/query.php
    index b2b6809e29..3f4f595e5c 100644
    a b function generate_postdata( $post ) { 
    11671167 *
    11681168 * @since 5.x
    11691169 *
    1170  * @global WP_Query $wp_query WordPress Query object.
    1171  *
    1172  * @param WP_Query|WP_Post[] $iterable WordPress query object or an array of posts.
     1170 * @global WP_Query                    $wp_query WordPress query object.
     1171 * @global WP_Post                     $post     WordPress post object.
    11731172 *
     1173 * @param WP_Query|Iterator|array|null $iterable WordPress query object or an array of posts.
    11741174 * @return Generator
    11751175 */
    11761176function wp_loop( $iterable = null ) {
    11771177
     1178        // If no iterable was passed, use the global query
    11781179        if ( null === $iterable ) {
    11791180                $iterable = $GLOBALS['wp_query'];
    11801181        }
    11811182
    1182         $posts = $iterable;
    1183 
     1183        // If the iterable contains a posts property, use that as the iterable
    11841184        if ( is_object( $iterable ) && property_exists( $iterable, 'posts' ) ) {
    1185                 $posts = $iterable->posts;
     1185                $iterable = $iterable->posts;
    11861186        }
    11871187
    1188         if ( ! is_array( $posts ) ) {
     1188        // If we don't have a valid iterable, return false and trigger message
     1189        if ( ! is_iterable( $iterable ) ) {
    11891190                /* translators: Data type */
    1190                 _doing_it_wrong( __FUNCTION__, sprintf( __( 'Expected an array, received %s instead.' ), gettype( $posts ) ), '3.1.0' );
     1191                _doing_it_wrong( __FUNCTION__, sprintf( __( 'Expected an iterable, received %s instead.' ), gettype( $iterable ) ), '5.x' );
    11911192
    11921193                return false;
    11931194        }
    function wp_loop( $iterable = null ) { 
    11981199        $save_post = $post;
    11991200
    12001201        try {
    1201                 foreach ( $posts as $post ) {
     1202                foreach ( $iterable as $post ) {
     1203
     1204                        // Ensure that we always yield a WP_Post object
     1205                        if ( ! ( $post instanceof WP_Post ) ) {
     1206                                $post = get_post( $post );
     1207                        }
    12021208
    12031209                        setup_postdata( $post );
    12041210                        yield $post;
    1205 
    12061211                }
    12071212        } finally {
    1208 
    12091213                wp_reset_postdata();
    12101214                // Restore the global post object
    12111215                $post = $save_post;
    1212 
    12131216        }
    12141217}