'+
''+
'escapeJs($container['name'])}_<%- data.id %>" '+
- 'name="widget_instance[<%- data.id %>][{$block->escapeJs($container['name'])}][for]" '+
+ 'id="all_{$escaper->escapeJs($container['name'])}_<%- data.id %>" '+
+ 'name="widget_instance[<%- data.id %>][{$escaper->escapeJs($container['name'])}][for]" '+
'value="all" checked="checked" /> '+
- ' '+
+ ' '+
'escapeJs($container['name'])}_<%- data.id %>" '+
- 'name="widget_instance[<%- data.id %>][{$block->escapeJs($container['name'])}][for]" '+
+ 'id="specific_{$escaper->escapeJs($container['name'])}_<%- data.id %>" '+
+ 'name="widget_instance[<%- data.id %>][{$escaper->escapeJs($container['name'])}][for]" '+
'value="specific" /> '+
- ''+
+ ''+
script;
$scriptString1 = $secureRenderer->renderEventListenerAsTag(
"onclick",
"WidgetInstance.togglePageGroupChooser(this)",
- "all_" . $block->escapeJs($container['name']) . "_<%- data.id %>"
+ "#all_" . $escaper->escapeJs($container['name']) . "_<%- data.id %>"
);
- $scriptString .= "'" . $block->escapeJs($scriptString1) . "'+" . PHP_EOL;
+ $scriptString .= "'" . $escaper->escapeJs($scriptString1) . "'+" . PHP_EOL;
$scriptString1 = $secureRenderer->renderEventListenerAsTag(
"onclick",
"WidgetInstance.togglePageGroupChooser(this)",
- "specific_" . $block->escapeJs($container['name']) . "_<%- data.id %>"
+ "#specific_" . $escaper->escapeJs($container['name']) . "_<%- data.id %>"
);
- $scriptString .= "'" . $block->escapeJs($scriptString1) . "'+" . PHP_EOL;
+ $scriptString .= "'" . $escaper->escapeJs($scriptString1) . "'+" . PHP_EOL;
$scriptString .= <<
-
-
-
-
-
+
+
+
+
+
+
+```
+
+The widget can then be initialized on a file upload form the following way:
+
+```js
+$('#fileupload').fileupload();
+```
+
+For further information, please refer to the following guides:
+
+- [Main documentation page](https://github.com/blueimp/jQuery-File-Upload/wiki)
+- [List of all available Options](https://github.com/blueimp/jQuery-File-Upload/wiki/Options)
+- [The plugin API](https://github.com/blueimp/jQuery-File-Upload/wiki/API)
+- [How to setup the plugin on your website](https://github.com/blueimp/jQuery-File-Upload/wiki/Setup)
+- [How to use only the basic plugin.](https://github.com/blueimp/jQuery-File-Upload/wiki/Basic-plugin)
+
+## Requirements
+
+### Mandatory requirements
+
+- [jQuery](https://jquery.com/) v1.7+
+- [jQuery UI widget factory](https://api.jqueryui.com/jQuery.widget/) v1.9+
+ (included): Required for the basic File Upload plugin, but very lightweight
+ without any other dependencies from the jQuery UI suite.
+- [jQuery Iframe Transport plugin](https://github.com/blueimp/jQuery-File-Upload/blob/master/js/jquery.iframe-transport.js)
+ (included): Required for
+ [browsers without XHR file upload support](https://github.com/blueimp/jQuery-File-Upload/wiki/Browser-support).
+
+### Optional requirements
+
+- [JavaScript Templates engine](https://github.com/blueimp/JavaScript-Templates)
+ v3+: Used to render the selected and uploaded files for the Basic Plus UI and
+ jQuery UI versions.
+- [JavaScript Load Image library](https://github.com/blueimp/JavaScript-Load-Image)
+ v2+: Required for the image previews and resizing functionality.
+- [JavaScript Canvas to Blob polyfill](https://github.com/blueimp/JavaScript-Canvas-to-Blob)
+ v3+:Required for the image previews and resizing functionality.
+- [blueimp Gallery](https://github.com/blueimp/Gallery) v2+: Used to display the
+ uploaded images in a lightbox.
+- [Bootstrap](https://getbootstrap.com/) v3+: Used for the demo design.
+- [Glyphicons](https://glyphicons.com/) Icon set used by Bootstrap.
+
+### Cross-domain requirements
+
+[Cross-domain File Uploads](https://github.com/blueimp/jQuery-File-Upload/wiki/Cross-domain-uploads)
+using the
+[Iframe Transport plugin](https://github.com/blueimp/jQuery-File-Upload/blob/master/js/jquery.iframe-transport.js)
+require a redirect back to the origin server to retrieve the upload results. The
+[example implementation](https://github.com/blueimp/jQuery-File-Upload/blob/master/js/main.js)
+makes use of
+[result.html](https://github.com/blueimp/jQuery-File-Upload/blob/master/cors/result.html)
+as a static redirect page for the origin server.
+
+The repository also includes the
+[jQuery XDomainRequest Transport plugin](https://github.com/blueimp/jQuery-File-Upload/blob/master/js/cors/jquery.xdr-transport.js),
+which enables limited cross-domain AJAX requests in Microsoft Internet Explorer
+8 and 9 (IE 10 supports cross-domain XHR requests).
+The XDomainRequest object allows GET and POST requests only and doesn't support
+file uploads. It is used on the
+[Demo](https://blueimp.github.io/jQuery-File-Upload/) to delete uploaded files
+from the cross-domain demo file upload service.
+
+## Browsers
+
+### Desktop browsers
+
+The File Upload plugin is regularly tested with the latest browser versions and
+supports the following minimal versions:
+
+- Google Chrome
+- Apple Safari 4.0+
+- Mozilla Firefox 3.0+
+- Opera 11.0+
+- Microsoft Internet Explorer 6.0+
+
+### Mobile browsers
+
+The File Upload plugin has been tested with and supports the following mobile
+browsers:
+
+- Apple Safari on iOS 6.0+
+- Google Chrome on iOS 6.0+
+- Google Chrome on Android 4.0+
+- Default Browser on Android 2.3+
+- Opera Mobile 12.0+
+
+### Extended browser support information
+
+For a detailed overview of the features supported by each browser version and
+known operating system / browser bugs, please have a look at the
+[Extended browser support information](https://github.com/blueimp/jQuery-File-Upload/wiki/Browser-support).
+
+## Testing
+
+The project comes with three sets of tests:
+
+1. Code linting using [ESLint](https://eslint.org/).
+2. Unit tests using [Mocha](https://mochajs.org/).
+3. End-to-end tests using [blueimp/wdio](https://github.com/blueimp/wdio).
+
+To run the tests, follow these steps:
+
+1. Start [Docker](https://docs.docker.com/).
+2. Install development dependencies:
+ ```sh
+ npm install
+ ```
+3. Run the tests:
+ ```sh
+ npm test
+ ```
+
+## Support
+
+This project is actively maintained, but there is no official support channel.
+If you have a question that another developer might help you with, please post
+to
+[Stack Overflow](https://stackoverflow.com/questions/tagged/blueimp+jquery+file-upload)
+and tag your question with `blueimp jquery file upload`.
+
+## License
+
+Released under the [MIT license](https://opensource.org/licenses/MIT).
diff --git a/lib/web/jquery/fileUploader/SECURITY.md b/lib/web/jquery/fileUploader/SECURITY.md
new file mode 100644
index 0000000000000..433a6853cdb3a
--- /dev/null
+++ b/lib/web/jquery/fileUploader/SECURITY.md
@@ -0,0 +1,227 @@
+# File Upload Security
+
+## Contents
+
+- [Introduction](#introduction)
+- [Purpose of this project](#purpose-of-this-project)
+- [Mitigations against file upload risks](#mitigations-against-file-upload-risks)
+ - [Prevent code execution on the server](#prevent-code-execution-on-the-server)
+ - [Prevent code execution in the browser](#prevent-code-execution-in-the-browser)
+ - [Prevent distribution of malware](#prevent-distribution-of-malware)
+- [Secure file upload serving configurations](#secure-file-upload-serving-configurations)
+ - [Apache config](#apache-config)
+ - [NGINX config](#nginx-config)
+- [Secure image processing configurations](#secure-image-processing-configurations)
+- [ImageMagick config](#imagemagick-config)
+
+## Introduction
+
+For an in-depth understanding of the potential security risks of providing file
+uploads and possible mitigations, please refer to the
+[OWASP - Unrestricted File Upload](https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload)
+documentation.
+
+To securely setup the project to serve uploaded files, please refer to the
+sample
+[Secure file upload serving configurations](#secure-file-upload-serving-configurations).
+
+To mitigate potential vulnerabilities in image processing libraries, please
+refer to the
+[Secure image processing configurations](#secure-image-processing-configurations).
+
+By default, all sample upload handlers allow only upload of image files, which
+mitigates some attack vectors, but should not be relied on as the only
+protection.
+
+Please also have a look at the
+[list of fixed vulnerabilities](VULNERABILITIES.md) in jQuery File Upload, which
+relates mostly to the sample server-side upload handlers and how they have been
+configured.
+
+## Purpose of this project
+
+Please note that this project is not a complete file management product, but
+foremost a client-side file upload library for [jQuery](https://jquery.com/).
+The server-side sample upload handlers are just examples to demonstrate the
+client-side file upload functionality.
+
+To make this very clear, there is **no user authentication** by default:
+
+- **everyone can upload files**
+- **everyone can delete uploaded files**
+
+In some cases this can be acceptable, but for most projects you will want to
+extend the sample upload handlers to integrate user authentication, or implement
+your own.
+
+It is also up to you to configure your web server to securely serve the uploaded
+files, e.g. using the
+[sample server configurations](#secure-file-upload-serving-configurations).
+
+## Mitigations against file upload risks
+
+### Prevent code execution on the server
+
+To prevent execution of scripts or binaries on server-side, the upload directory
+must be configured to not execute files in the upload directory (e.g.
+`server/php/files` as the default for the PHP upload handler) and only treat
+uploaded files as static content.
+
+The recommended way to do this is to configure the upload directory path to
+point outside of the web application root.
+Then the web server can be configured to serve files from the upload directory
+with their default static files handler only.
+
+Limiting file uploads to a whitelist of safe file types (e.g. image files) also
+mitigates this issue, but should not be the only protection.
+
+### Prevent code execution in the browser
+
+To prevent execution of scripts on client-side, the following headers must be
+sent when delivering generic uploaded files to the client:
+
+```
+Content-Type: application/octet-stream
+X-Content-Type-Options: nosniff
+```
+
+The `Content-Type: application/octet-stream` header instructs browsers to
+display a download dialog instead of parsing it and possibly executing script
+content e.g. in HTML files.
+
+The `X-Content-Type-Options: nosniff` header prevents browsers to try to detect
+the file mime type despite the given content-type header.
+
+For known safe files, the content-type header can be adjusted using a
+**whitelist**, e.g. sending `Content-Type: image/png` for PNG files.
+
+### Prevent distribution of malware
+
+To prevent attackers from uploading and distributing malware (e.g. computer
+viruses), it is recommended to limit file uploads only to a whitelist of safe
+file types.
+
+Please note that the detection of file types in the sample file upload handlers
+is based on the file extension and not the actual file content. This makes it
+still possible for attackers to upload malware by giving their files an image
+file extension, but should prevent automatic execution on client computers when
+opening those files.
+
+It does not protect at all from exploiting vulnerabilities in image display
+programs, nor from users renaming file extensions to inadvertently execute the
+contained malicious code.
+
+## Secure file upload serving configurations
+
+The following configurations serve uploaded files as static files with the
+proper headers as
+[mitigation against file upload risks](#mitigations-against-file-upload-risks).
+Please do not simply copy&paste these configurations, but make sure you
+understand what they are doing and that you have implemented them correctly.
+
+> Always test your own setup and make sure that it is secure!
+
+e.g. try uploading PHP scripts (as "example.php", "example.php.png" and
+"example.png") to see if they get executed by your web server, e.g. the content
+of the following sample:
+
+```php
+GIF89ad
+ # Some of the directives require the Apache Headers module. If it is not
+ # already enabled, please execute the following command and reload Apache:
+ # sudo a2enmod headers
+ #
+ # Please note that the order of directives across configuration files matters,
+ # see also:
+ # https://httpd.apache.org/docs/current/sections.html#merging
+
+ # The following directive matches all files and forces them to be handled as
+ # static content, which prevents the server from parsing and executing files
+ # that are associated with a dynamic runtime, e.g. PHP files.
+ # It also forces their Content-Type header to "application/octet-stream" and
+ # adds a "Content-Disposition: attachment" header to force a download dialog,
+ # which prevents browsers from interpreting files in the context of the
+ # web server, e.g. HTML files containing JavaScript.
+ # Lastly it also prevents browsers from MIME-sniffing the Content-Type,
+ # preventing them from interpreting a file as a different Content-Type than
+ # the one sent by the webserver.
+
+ SetHandler default-handler
+ ForceType application/octet-stream
+ Header set Content-Disposition attachment
+ Header set X-Content-Type-Options nosniff
+
+
+ # The following directive matches known image files and unsets the forced
+ # Content-Type so they can be served with their original mime type.
+ # It also unsets the Content-Disposition header to allow displaying them
+ # inline in the browser.
+
+ ForceType none
+ Header unset Content-Disposition
+
+
+```
+
+### NGINX config
+
+Add the following directive to the NGINX config, replacing the directory path
+with the absolute path to the upload directory:
+
+```Nginx
+location ^~ /path/to/project/server/php/files {
+ root html;
+ default_type application/octet-stream;
+ types {
+ image/gif gif;
+ image/jpeg jpg;
+ image/png png;
+ }
+ add_header X-Content-Type-Options 'nosniff';
+ if ($request_filename ~ /(((?!\.(jpg)|(png)|(gif)$)[^/])+$)) {
+ add_header Content-Disposition 'attachment; filename="$1"';
+ # Add X-Content-Type-Options again, as using add_header in a new context
+ # dismisses all previous add_header calls:
+ add_header X-Content-Type-Options 'nosniff';
+ }
+}
+```
+
+## Secure image processing configurations
+
+The following configuration mitigates
+[potential image processing vulnerabilities with ImageMagick](VULNERABILITIES.md#potential-vulnerabilities-with-php-imagemagick)
+by limiting the attack vectors to a small subset of image types
+(`GIF/JPEG/PNG`).
+
+Please also consider using alternative, safer image processing libraries like
+[libvips](https://github.com/libvips/libvips) or
+[imageflow](https://github.com/imazen/imageflow).
+
+## ImageMagick config
+
+It is recommended to disable all non-required ImageMagick coders via
+[policy.xml](https://wiki.debian.org/imagemagick/security).
+To do so, locate the ImageMagick `policy.xml` configuration file and add the
+following policies:
+
+```xml
+
+
+
+
+
+
+
+
+```
diff --git a/lib/web/jquery/fileUploader/canvas-to-blob.js b/lib/web/jquery/fileUploader/canvas-to-blob.js
deleted file mode 100644
index 4e855b9f3a592..0000000000000
--- a/lib/web/jquery/fileUploader/canvas-to-blob.js
+++ /dev/null
@@ -1 +0,0 @@
-(function(a){"use strict";var b=a.HTMLCanvasElement&&a.HTMLCanvasElement.prototype,c=a.Blob&&function(){try{return Boolean(new Blob)}catch(a){return!1}}(),d=c&&a.Uint8Array&&function(){try{return(new Blob([new Uint8Array(100)])).size===100}catch(a){return!1}}(),e=a.BlobBuilder||a.WebKitBlobBuilder||a.MozBlobBuilder||a.MSBlobBuilder,f=(c||e)&&a.atob&&a.ArrayBuffer&&a.Uint8Array&&function(a){var b,f,g,h,i,j;a.split(",")[0].indexOf("base64")>=0?b=atob(a.split(",")[1]):b=decodeURIComponent(a.split(",")[1]),f=new ArrayBuffer(b.length),g=new Uint8Array(f);for(h=0;h').prop('href', options.postMessage)[0],
- target = loc.protocol + '//' + loc.host,
- xhrUpload = options.xhr().upload;
- return {
- send: function (_, completeCallback) {
- var message = {
- id: 'postmessage-transport-' + (counter += 1)
- },
- eventName = 'message.' + message.id;
- iframe = $(
- ''
- ).bind('load', function () {
- $.each(names, function (i, name) {
- message[name] = options[name];
- });
- message.dataType = message.dataType.replace('postmessage ', '');
- $(window).bind(eventName, function (e) {
- e = e.originalEvent;
- var data = e.data,
- ev;
- if (e.origin === target && data.id === message.id) {
- if (data.type === 'progress') {
- ev = document.createEvent('Event');
- ev.initEvent(data.type, false, true);
- $.extend(ev, data);
- xhrUpload.dispatchEvent(ev);
- } else {
- completeCallback(
- data.status,
- data.statusText,
- {postmessage: data.result},
- data.headers
- );
- iframe.remove();
- $(window).unbind(eventName);
- }
- }
- });
- iframe[0].contentWindow.postMessage(
- message,
- target
- );
- }).appendTo(document.body);
- },
- abort: function () {
- if (iframe) {
- iframe.remove();
- }
+ $.ajaxTransport('postmessage', function (options) {
+ if (options.postMessage && window.postMessage) {
+ var iframe,
+ loc = $('').prop('href', options.postMessage)[0],
+ target = loc.protocol + '//' + loc.host,
+ xhrUpload = options.xhr().upload;
+ // IE always includes the port for the host property of a link
+ // element, but not in the location.host or origin property for the
+ // default http port 80 and https port 443, so we strip it:
+ if (/^(http:\/\/.+:80)|(https:\/\/.+:443)$/.test(target)) {
+ target = target.replace(/:(80|443)$/, '');
+ }
+ return {
+ send: function (_, completeCallback) {
+ counter += 1;
+ var message = {
+ id: 'postmessage-transport-' + counter
+ },
+ eventName = 'message.' + message.id;
+ iframe = $(
+ ''
+ )
+ .on('load', function () {
+ $.each(names, function (i, name) {
+ message[name] = options[name];
+ });
+ message.dataType = message.dataType.replace('postmessage ', '');
+ $(window).on(eventName, function (event) {
+ var e = event.originalEvent;
+ var data = e.data;
+ var ev;
+ if (e.origin === target && data.id === message.id) {
+ if (data.type === 'progress') {
+ ev = document.createEvent('Event');
+ ev.initEvent(data.type, false, true);
+ $.extend(ev, data);
+ xhrUpload.dispatchEvent(ev);
+ } else {
+ completeCallback(
+ data.status,
+ data.statusText,
+ { postmessage: data.result },
+ data.headers
+ );
+ iframe.remove();
+ $(window).off(eventName);
+ }
}
- };
+ });
+ iframe[0].contentWindow.postMessage(message, target);
+ })
+ .appendTo(document.body);
+ },
+ abort: function () {
+ if (iframe) {
+ iframe.remove();
+ }
}
- });
-
-}));
+ };
+ }
+ });
+});
diff --git a/lib/web/jquery/fileUploader/cors/jquery.xdr-transport.js b/lib/web/jquery/fileUploader/cors/jquery.xdr-transport.js
index c42c54828d8ff..9e81860b943fc 100644
--- a/lib/web/jquery/fileUploader/cors/jquery.xdr-transport.js
+++ b/lib/web/jquery/fileUploader/cors/jquery.xdr-transport.js
@@ -1,85 +1,97 @@
/*
- * jQuery XDomainRequest Transport Plugin 1.1.2
+ * jQuery XDomainRequest Transport Plugin
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
- * http://www.opensource.org/licenses/MIT
+ * https://opensource.org/licenses/MIT
*
* Based on Julian Aubourg's ajaxHooks xdr.js:
* https://github.com/jaubourg/ajaxHooks/
*/
-/*jslint unparam: true */
-/*global define, window, XDomainRequest */
+/* global define, require, XDomainRequest */
(function (factory) {
- 'use strict';
- if (typeof define === 'function' && define.amd) {
- // Register as an anonymous AMD module:
- define(['jquery'], factory);
- } else {
- // Browser globals:
- factory(window.jQuery);
- }
-}(function ($) {
- 'use strict';
- if (window.XDomainRequest && !$.support.cors) {
- $.ajaxTransport(function (s) {
- if (s.crossDomain && s.async) {
- if (s.timeout) {
- s.xdrTimeout = s.timeout;
- delete s.timeout;
- }
- var xdr;
- return {
- send: function (headers, completeCallback) {
- function callback(status, statusText, responses, responseHeaders) {
- xdr.onload = xdr.onerror = xdr.ontimeout = $.noop;
- xdr = null;
- completeCallback(status, statusText, responses, responseHeaders);
- }
- xdr = new XDomainRequest();
- // XDomainRequest only supports GET and POST:
- if (s.type === 'DELETE') {
- s.url = s.url + (/\?/.test(s.url) ? '&' : '?') +
- '_method=DELETE';
- s.type = 'POST';
- } else if (s.type === 'PUT') {
- s.url = s.url + (/\?/.test(s.url) ? '&' : '?') +
- '_method=PUT';
- s.type = 'POST';
- }
- xdr.open(s.type, s.url);
- xdr.onload = function () {
- callback(
- 200,
- 'OK',
- {text: xdr.responseText},
- 'Content-Type: ' + xdr.contentType
- );
- };
- xdr.onerror = function () {
- callback(404, 'Not Found');
- };
- if (s.xdrTimeout) {
- xdr.ontimeout = function () {
- callback(0, 'timeout');
- };
- xdr.timeout = s.xdrTimeout;
- }
- xdr.send((s.hasContent && s.data) || null);
- },
- abort: function () {
- if (xdr) {
- xdr.onerror = $.noop();
- xdr.abort();
- }
- }
- };
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery'], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(require('jquery'));
+ } else {
+ // Browser globals:
+ factory(window.jQuery);
+ }
+})(function ($) {
+ 'use strict';
+ if (window.XDomainRequest && !$.support.cors) {
+ $.ajaxTransport(function (s) {
+ if (s.crossDomain && s.async) {
+ if (s.timeout) {
+ s.xdrTimeout = s.timeout;
+ delete s.timeout;
+ }
+ var xdr;
+ return {
+ send: function (headers, completeCallback) {
+ var addParamChar = /\?/.test(s.url) ? '&' : '?';
+ /**
+ * Callback wrapper function
+ *
+ * @param {number} status HTTP status code
+ * @param {string} statusText HTTP status text
+ * @param {object} [responses] Content-type specific responses
+ * @param {string} [responseHeaders] Response headers string
+ */
+ function callback(status, statusText, responses, responseHeaders) {
+ xdr.onload = xdr.onerror = xdr.ontimeout = $.noop;
+ xdr = null;
+ completeCallback(status, statusText, responses, responseHeaders);
}
- });
- }
-}));
+ xdr = new XDomainRequest();
+ // XDomainRequest only supports GET and POST:
+ if (s.type === 'DELETE') {
+ s.url = s.url + addParamChar + '_method=DELETE';
+ s.type = 'POST';
+ } else if (s.type === 'PUT') {
+ s.url = s.url + addParamChar + '_method=PUT';
+ s.type = 'POST';
+ } else if (s.type === 'PATCH') {
+ s.url = s.url + addParamChar + '_method=PATCH';
+ s.type = 'POST';
+ }
+ xdr.open(s.type, s.url);
+ xdr.onload = function () {
+ callback(
+ 200,
+ 'OK',
+ { text: xdr.responseText },
+ 'Content-Type: ' + xdr.contentType
+ );
+ };
+ xdr.onerror = function () {
+ callback(404, 'Not Found');
+ };
+ if (s.xdrTimeout) {
+ xdr.ontimeout = function () {
+ callback(0, 'timeout');
+ };
+ xdr.timeout = s.xdrTimeout;
+ }
+ xdr.send((s.hasContent && s.data) || null);
+ },
+ abort: function () {
+ if (xdr) {
+ xdr.onerror = $.noop();
+ xdr.abort();
+ }
+ }
+ };
+ }
+ });
+ }
+});
diff --git a/lib/web/jquery/fileUploader/css/jquery.fileupload-noscript.css b/lib/web/jquery/fileUploader/css/jquery.fileupload-noscript.css
new file mode 100644
index 0000000000000..2409bfb0a6942
--- /dev/null
+++ b/lib/web/jquery/fileUploader/css/jquery.fileupload-noscript.css
@@ -0,0 +1,22 @@
+@charset "UTF-8";
+/*
+ * jQuery File Upload Plugin NoScript CSS
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+.fileinput-button input {
+ position: static;
+ opacity: 1;
+ filter: none;
+ font-size: inherit !important;
+ direction: inherit;
+}
+.fileinput-button span {
+ display: none;
+}
diff --git a/lib/web/jquery/fileUploader/css/jquery.fileupload-ui-noscript.css b/lib/web/jquery/fileUploader/css/jquery.fileupload-ui-noscript.css
new file mode 100644
index 0000000000000..30651acf026c0
--- /dev/null
+++ b/lib/web/jquery/fileUploader/css/jquery.fileupload-ui-noscript.css
@@ -0,0 +1,17 @@
+@charset "UTF-8";
+/*
+ * jQuery File Upload UI Plugin NoScript CSS
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2012, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+.fileinput-button i,
+.fileupload-buttonbar .delete,
+.fileupload-buttonbar .toggle {
+ display: none;
+}
diff --git a/lib/web/jquery/fileUploader/css/jquery.fileupload-ui.css b/lib/web/jquery/fileUploader/css/jquery.fileupload-ui.css
index 44b628efb481c..a6cfc7529198b 100644
--- a/lib/web/jquery/fileUploader/css/jquery.fileupload-ui.css
+++ b/lib/web/jquery/fileUploader/css/jquery.fileupload-ui.css
@@ -1,73 +1,68 @@
-@charset 'UTF-8';
+@charset "UTF-8";
/*
- * jQuery File Upload UI Plugin CSS 6.3
+ * jQuery File Upload UI Plugin CSS
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2010, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
- * http://www.opensource.org/licenses/MIT
+ * https://opensource.org/licenses/MIT
*/
-.fileinput-button {
- position: relative;
- overflow: hidden;
- float: left;
- margin-right: 4px;
-}
-.fileinput-button input {
- position: absolute;
- top: 0;
- right: 0;
- margin: 0;
- border: solid transparent;
- border-width: 0 0 100px 200px;
- opacity: 0;
- filter: alpha(opacity=0);
- -moz-transform: translate(-300px, 0) scale(4);
- direction: ltr;
- cursor: pointer;
-}
-.fileupload-buttonbar .btn,
-.fileupload-buttonbar .toggle {
- margin-bottom: 5px;
-}
-.files .progress {
- width: 200px;
-}
+.progress-animated .progress-bar,
.progress-animated .bar {
- background: url(../img/progressbar.gif) !important;
+ background: url('../img/progressbar.gif') !important;
filter: none;
}
-.fileupload-loading {
- position: absolute;
- left: 50%;
- width: 128px;
- height: 128px;
- background: url(../img/loading.gif) center no-repeat;
+.fileupload-process {
+ float: right;
display: none;
}
-.fileupload-processing .fileupload-loading {
+.fileupload-processing .fileupload-process,
+.files .processing .preview {
display: block;
+ width: 32px;
+ height: 32px;
+ background: url('../img/loading.gif') center no-repeat;
+ background-size: contain;
+}
+.files audio,
+.files video {
+ max-width: 300px;
+}
+.files .name {
+ word-wrap: break-word;
+ overflow-wrap: anywhere;
+ -webkit-hyphens: auto;
+ hyphens: auto;
+}
+.files button {
+ margin-bottom: 5px;
+}
+.toggle[type='checkbox'] {
+ transform: scale(2);
+ margin-left: 10px;
}
-@media (max-width: 480px) {
+@media (max-width: 767px) {
+ .fileupload-buttonbar .btn {
+ margin-bottom: 5px;
+ }
+ .fileupload-buttonbar .delete,
+ .fileupload-buttonbar .toggle,
+ .files .toggle,
.files .btn span {
display: none;
}
- .files .preview * {
- width: 40px;
- }
- .files .name * {
- width: 80px;
- display: inline-block;
- word-wrap: break-word;
- }
- .files .progress {
- width: 20px;
+ .files audio,
+ .files video {
+ max-width: 80px;
}
- .files .delete {
- width: 60px;
+}
+
+@media (max-width: 480px) {
+ .files .image td:nth-child(2) {
+ display: none;
}
}
diff --git a/lib/web/jquery/fileUploader/css/jquery.fileupload.css b/lib/web/jquery/fileUploader/css/jquery.fileupload.css
new file mode 100644
index 0000000000000..5716f3e8a8aea
--- /dev/null
+++ b/lib/web/jquery/fileUploader/css/jquery.fileupload.css
@@ -0,0 +1,36 @@
+@charset "UTF-8";
+/*
+ * jQuery File Upload Plugin CSS
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+.fileinput-button {
+ position: relative;
+ overflow: hidden;
+ display: inline-block;
+}
+.fileinput-button input {
+ position: absolute;
+ top: 0;
+ right: 0;
+ margin: 0;
+ height: 100%;
+ opacity: 0;
+ filter: alpha(opacity=0);
+ font-size: 200px !important;
+ direction: ltr;
+ cursor: pointer;
+}
+
+/* Fixes for IE < 8 */
+@media screen\9 {
+ .fileinput-button input {
+ font-size: 150% !important;
+ }
+}
diff --git a/lib/web/jquery/fileUploader/img/loading.gif b/lib/web/jquery/fileUploader/img/loading.gif
index 4ae663fa730eb..90f28cbdbb390 100644
Binary files a/lib/web/jquery/fileUploader/img/loading.gif and b/lib/web/jquery/fileUploader/img/loading.gif differ
diff --git a/lib/web/jquery/fileUploader/img/progressbar.gif b/lib/web/jquery/fileUploader/img/progressbar.gif
index 74bb94e8e5d2b..fbcce6bc9abfc 100644
Binary files a/lib/web/jquery/fileUploader/img/progressbar.gif and b/lib/web/jquery/fileUploader/img/progressbar.gif differ
diff --git a/lib/web/jquery/fileUploader/jquery.fileupload-audio.js b/lib/web/jquery/fileUploader/jquery.fileupload-audio.js
new file mode 100644
index 0000000000000..1435ef20af2f6
--- /dev/null
+++ b/lib/web/jquery/fileUploader/jquery.fileupload-audio.js
@@ -0,0 +1,101 @@
+/*
+ * jQuery File Upload Audio Preview Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, require */
+
+(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery', 'jquery/fileUploader/vendor/blueimp-load-image/js/load-image', 'jquery/fileUploader/jquery.fileupload-process'], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image'),
+ require('jquery/fileUploader/jquery.fileupload-process')
+ );
+ } else {
+ // Browser globals:
+ factory(window.jQuery, window.loadImage);
+ }
+})(function ($, loadImage) {
+ 'use strict';
+
+ // Prepend to the default processQueue:
+ $.blueimp.fileupload.prototype.options.processQueue.unshift(
+ {
+ action: 'loadAudio',
+ // Use the action as prefix for the "@" options:
+ prefix: true,
+ fileTypes: '@',
+ maxFileSize: '@',
+ disabled: '@disableAudioPreview'
+ },
+ {
+ action: 'setAudio',
+ name: '@audioPreviewName',
+ disabled: '@disableAudioPreview'
+ }
+ );
+
+ // The File Upload Audio Preview plugin extends the fileupload widget
+ // with audio preview functionality:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+ options: {
+ // The regular expression for the types of audio files to load,
+ // matched against the file type:
+ loadAudioFileTypes: /^audio\/.*$/
+ },
+
+ _audioElement: document.createElement('audio'),
+
+ processActions: {
+ // Loads the audio file given via data.files and data.index
+ // as audio element if the browser supports playing it.
+ // Accepts the options fileTypes (regular expression)
+ // and maxFileSize (integer) to limit the files to load:
+ loadAudio: function (data, options) {
+ if (options.disabled) {
+ return data;
+ }
+ var file = data.files[data.index],
+ url,
+ audio;
+ if (
+ this._audioElement.canPlayType &&
+ this._audioElement.canPlayType(file.type) &&
+ ($.type(options.maxFileSize) !== 'number' ||
+ file.size <= options.maxFileSize) &&
+ (!options.fileTypes || options.fileTypes.test(file.type))
+ ) {
+ url = loadImage.createObjectURL(file);
+ if (url) {
+ audio = this._audioElement.cloneNode(false);
+ audio.src = url;
+ audio.controls = true;
+ data.audio = audio;
+ return data;
+ }
+ }
+ return data;
+ },
+
+ // Sets the audio element as a property of the file object:
+ setAudio: function (data, options) {
+ if (data.audio && !options.disabled) {
+ data.files[data.index][options.name || 'preview'] = data.audio;
+ }
+ return data;
+ }
+ }
+ });
+});
diff --git a/lib/web/jquery/fileUploader/jquery.fileupload-fp.js b/lib/web/jquery/fileUploader/jquery.fileupload-fp.js
deleted file mode 100644
index ee8f46342a93a..0000000000000
--- a/lib/web/jquery/fileUploader/jquery.fileupload-fp.js
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * jQuery File Upload File Processing Plugin 1.0
- * https://github.com/blueimp/jQuery-File-Upload
- *
- * Copyright 2012, Sebastian Tschan
- * https://blueimp.net
- *
- * Licensed under the MIT license:
- * http://www.opensource.org/licenses/MIT
- */
-
-/*jslint nomen: true, unparam: true, regexp: true */
-/*global define, window, document */
-
-(function (factory) {
- 'use strict';
- if (typeof define === 'function' && define.amd) {
- // Register as an anonymous AMD module:
- define([
- 'jquery',
- 'jquery/fileUploader/load-image',
- 'jquery/fileUploader/canvas-to-blob',
- 'jquery/fileUploader/jquery.fileupload'
- ], factory);
- } else {
- // Browser globals:
- factory(
- window.jQuery,
- window.loadImage
- );
- }
-}(function ($, loadImage) {
- 'use strict';
-
- // The File Upload IP version extends the basic fileupload widget
- // with file processing functionality:
- $.widget('blueimpFP.fileupload', $.blueimp.fileupload, {
-
- options: {
- // The list of file processing actions:
- process: [
- /*
- {
- action: 'load',
- fileTypes: /^image\/(gif|jpeg|png)$/,
- maxFileSize: 20000000 // 20MB
- },
- {
- action: 'resize',
- maxWidth: 1920,
- maxHeight: 1200,
- minWidth: 800,
- minHeight: 600
- },
- {
- action: 'save'
- }
- */
- ],
-
- // The add callback is invoked as soon as files are added to the
- // fileupload widget (via file input selection, drag & drop or add
- // API call). See the basic file upload widget for more information:
- add: function (e, data) {
- $(this).fileupload('process', data).done(function () {
- data.submit();
- });
- }
- },
-
- processActions: {
- // Loads the image given via data.files and data.index
- // as canvas element.
- // Accepts the options fileTypes (regular expression)
- // and maxFileSize (integer) to limit the files to load:
- load: function (data, options) {
- var that = this,
- file = data.files[data.index],
- dfd = $.Deferred();
- if (window.HTMLCanvasElement &&
- window.HTMLCanvasElement.prototype.toBlob &&
- ($.type(options.maxFileSize) !== 'number' ||
- file.size < options.maxFileSize) &&
- (!options.fileTypes ||
- options.fileTypes.test(file.type))) {
- loadImage(
- file,
- function (canvas) {
- data.canvas = canvas;
- dfd.resolveWith(that, [data]);
- },
- {canvas: true}
- );
- } else {
- dfd.rejectWith(that, [data]);
- }
- return dfd.promise();
- },
- // Resizes the image given as data.canvas and updates
- // data.canvas with the resized image.
- // Accepts the options maxWidth, maxHeight, minWidth and
- // minHeight to scale the given image:
- resize: function (data, options) {
- if (data.canvas) {
- var canvas = loadImage.scale(data.canvas, options);
- if (canvas.width !== data.canvas.width ||
- canvas.height !== data.canvas.height) {
- data.canvas = canvas;
- data.processed = true;
- }
- }
- return data;
- },
- // Saves the processed image given as data.canvas
- // inplace at data.index of data.files:
- save: function (data, options) {
- // Do nothing if no processing has happened:
- if (!data.canvas || !data.processed) {
- return data;
- }
- var that = this,
- file = data.files[data.index],
- name = file.name,
- dfd = $.Deferred(),
- callback = function (blob) {
- if (!blob.name) {
- if (file.type === blob.type) {
- blob.name = file.name;
- } else if (file.name) {
- blob.name = file.name.replace(
- /\..+$/,
- '.' + blob.type.substr(6)
- );
- }
- }
- // Store the created blob at the position
- // of the original file in the files list:
- data.files[data.index] = blob;
- dfd.resolveWith(that, [data]);
- };
- // Use canvas.mozGetAsFile directly, to retain the filename, as
- // Gecko doesn't support the filename option for FormData.append:
- if (data.canvas.mozGetAsFile) {
- callback(data.canvas.mozGetAsFile(
- (/^image\/(jpeg|png)$/.test(file.type) && name) ||
- ((name && name.replace(/\..+$/, '')) ||
- 'blob') + '.png',
- file.type
- ));
- } else {
- data.canvas.toBlob(callback, file.type);
- }
- return dfd.promise();
- }
- },
-
- // Resizes the file at the given index and stores the created blob at
- // the original position of the files list, returns a Promise object:
- _processFile: function (files, index, options) {
- var that = this,
- dfd = $.Deferred().resolveWith(that, [{
- files: files,
- index: index
- }]),
- chain = dfd.promise();
- that._processing += 1;
- $.each(options.process, function (i, settings) {
- chain = chain.pipe(function (data) {
- return that.processActions[settings.action]
- .call(this, data, settings);
- });
- });
- chain.always(function () {
- that._processing -= 1;
- if (that._processing === 0) {
- that.element
- .removeClass('fileupload-processing');
- }
- });
- if (that._processing === 1) {
- that.element.addClass('fileupload-processing');
- }
- return chain;
- },
-
- // Processes the files given as files property of the data parameter,
- // returns a Promise object that allows to bind a done handler, which
- // will be invoked after processing all files (inplace) is done:
- process: function (data) {
- var that = this,
- options = $.extend({}, this.options, data);
- if (options.process && options.process.length &&
- this._isXHRUpload(options)) {
- $.each(data.files, function (index, file) {
- that._processingQueue = that._processingQueue.pipe(
- function () {
- var dfd = $.Deferred();
- that._processFile(data.files, index, options)
- .always(function () {
- dfd.resolveWith(that);
- });
- return dfd.promise();
- }
- );
- });
- }
- return this._processingQueue;
- },
-
- _create: function () {
- $.blueimp.fileupload.prototype._create.call(this);
- this._processing = 0;
- this._processingQueue = $.Deferred().resolveWith(this)
- .promise();
- }
-
- });
-
-}));
diff --git a/lib/web/jquery/fileUploader/jquery.fileupload-image.js b/lib/web/jquery/fileUploader/jquery.fileupload-image.js
new file mode 100644
index 0000000000000..11c63c236247c
--- /dev/null
+++ b/lib/web/jquery/fileUploader/jquery.fileupload-image.js
@@ -0,0 +1,346 @@
+/*
+ * jQuery File Upload Image Preview & Resize Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, require */
+
+(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ 'jquery/fileUploader/vendor/blueimp-load-image/js/load-image',
+ 'jquery/fileUploader/vendor/blueimp-load-image/js/load-image-meta',
+ 'jquery/fileUploader/vendor/blueimp-load-image/js/load-image-scale',
+ 'jquery/fileUploader/vendor/blueimp-load-image/js/load-image-exif',
+ 'jquery/fileUploader/vendor/blueimp-load-image/js/load-image-orientation',
+ 'jquery/fileUploader/vendor/blueimp-canvas-to-blob/js/canvas-to-blob',
+ 'jquery/fileUploader/jquery.fileupload-process'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image'),
+ require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image-meta'),
+ require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image-scale'),
+ require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image-exif'),
+ require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image-orientation'),
+ require('jquery/fileUploader/vendor/blueimp-canvas-to-blob/js/canvas-to-blob'),
+ require('jquery/fileUploader/jquery.fileupload-process')
+ );
+ } else {
+ // Browser globals:
+ factory(window.jQuery, window.loadImage);
+ }
+})(function ($, loadImage) {
+ 'use strict';
+
+ // Prepend to the default processQueue:
+ $.blueimp.fileupload.prototype.options.processQueue.unshift(
+ {
+ action: 'loadImageMetaData',
+ maxMetaDataSize: '@',
+ disableImageHead: '@',
+ disableMetaDataParsers: '@',
+ disableExif: '@',
+ disableExifOffsets: '@',
+ includeExifTags: '@',
+ excludeExifTags: '@',
+ disableIptc: '@',
+ disableIptcOffsets: '@',
+ includeIptcTags: '@',
+ excludeIptcTags: '@',
+ disabled: '@disableImageMetaDataLoad'
+ },
+ {
+ action: 'loadImage',
+ // Use the action as prefix for the "@" options:
+ prefix: true,
+ fileTypes: '@',
+ maxFileSize: '@',
+ noRevoke: '@',
+ disabled: '@disableImageLoad'
+ },
+ {
+ action: 'resizeImage',
+ // Use "image" as prefix for the "@" options:
+ prefix: 'image',
+ maxWidth: '@',
+ maxHeight: '@',
+ minWidth: '@',
+ minHeight: '@',
+ crop: '@',
+ orientation: '@',
+ forceResize: '@',
+ disabled: '@disableImageResize'
+ },
+ {
+ action: 'saveImage',
+ quality: '@imageQuality',
+ type: '@imageType',
+ disabled: '@disableImageResize'
+ },
+ {
+ action: 'saveImageMetaData',
+ disabled: '@disableImageMetaDataSave'
+ },
+ {
+ action: 'resizeImage',
+ // Use "preview" as prefix for the "@" options:
+ prefix: 'preview',
+ maxWidth: '@',
+ maxHeight: '@',
+ minWidth: '@',
+ minHeight: '@',
+ crop: '@',
+ orientation: '@',
+ thumbnail: '@',
+ canvas: '@',
+ disabled: '@disableImagePreview'
+ },
+ {
+ action: 'setImage',
+ name: '@imagePreviewName',
+ disabled: '@disableImagePreview'
+ },
+ {
+ action: 'deleteImageReferences',
+ disabled: '@disableImageReferencesDeletion'
+ }
+ );
+
+ // The File Upload Resize plugin extends the fileupload widget
+ // with image resize functionality:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+ options: {
+ // The regular expression for the types of images to load:
+ // matched against the file type:
+ loadImageFileTypes: /^image\/(gif|jpeg|png|svg\+xml)$/,
+ // The maximum file size of images to load:
+ loadImageMaxFileSize: 10000000, // 10MB
+ // The maximum width of resized images:
+ imageMaxWidth: 1920,
+ // The maximum height of resized images:
+ imageMaxHeight: 1080,
+ // Defines the image orientation (1-8) or takes the orientation
+ // value from Exif data if set to true:
+ imageOrientation: true,
+ // Define if resized images should be cropped or only scaled:
+ imageCrop: false,
+ // Disable the resize image functionality by default:
+ disableImageResize: true,
+ // The maximum width of the preview images:
+ previewMaxWidth: 80,
+ // The maximum height of the preview images:
+ previewMaxHeight: 80,
+ // Defines the preview orientation (1-8) or takes the orientation
+ // value from Exif data if set to true:
+ previewOrientation: true,
+ // Create the preview using the Exif data thumbnail:
+ previewThumbnail: true,
+ // Define if preview images should be cropped or only scaled:
+ previewCrop: false,
+ // Define if preview images should be resized as canvas elements:
+ previewCanvas: true
+ },
+
+ processActions: {
+ // Loads the image given via data.files and data.index
+ // as img element, if the browser supports the File API.
+ // Accepts the options fileTypes (regular expression)
+ // and maxFileSize (integer) to limit the files to load:
+ loadImage: function (data, options) {
+ if (options.disabled) {
+ return data;
+ }
+ var that = this,
+ file = data.files[data.index],
+ // eslint-disable-next-line new-cap
+ dfd = $.Deferred();
+ if (
+ ($.type(options.maxFileSize) === 'number' &&
+ file.size > options.maxFileSize) ||
+ (options.fileTypes && !options.fileTypes.test(file.type)) ||
+ !loadImage(
+ file,
+ function (img) {
+ if (img.src) {
+ data.img = img;
+ }
+ dfd.resolveWith(that, [data]);
+ },
+ options
+ )
+ ) {
+ return data;
+ }
+ return dfd.promise();
+ },
+
+ // Resizes the image given as data.canvas or data.img
+ // and updates data.canvas or data.img with the resized image.
+ // Also stores the resized image as preview property.
+ // Accepts the options maxWidth, maxHeight, minWidth,
+ // minHeight, canvas and crop:
+ resizeImage: function (data, options) {
+ if (options.disabled || !(data.canvas || data.img)) {
+ return data;
+ }
+ // eslint-disable-next-line no-param-reassign
+ options = $.extend({ canvas: true }, options);
+ var that = this,
+ // eslint-disable-next-line new-cap
+ dfd = $.Deferred(),
+ img = (options.canvas && data.canvas) || data.img,
+ resolve = function (newImg) {
+ if (
+ newImg &&
+ (newImg.width !== img.width ||
+ newImg.height !== img.height ||
+ options.forceResize)
+ ) {
+ data[newImg.getContext ? 'canvas' : 'img'] = newImg;
+ }
+ data.preview = newImg;
+ dfd.resolveWith(that, [data]);
+ },
+ thumbnail,
+ thumbnailBlob;
+ if (data.exif && options.thumbnail) {
+ thumbnail = data.exif.get('Thumbnail');
+ thumbnailBlob = thumbnail && thumbnail.get('Blob');
+ if (thumbnailBlob) {
+ options.orientation = data.exif.get('Orientation');
+ loadImage(thumbnailBlob, resolve, options);
+ return dfd.promise();
+ }
+ }
+ if (data.orientation) {
+ // Prevent orienting the same image twice:
+ delete options.orientation;
+ } else {
+ data.orientation = options.orientation || loadImage.orientation;
+ }
+ if (img) {
+ resolve(loadImage.scale(img, options, data));
+ return dfd.promise();
+ }
+ return data;
+ },
+
+ // Saves the processed image given as data.canvas
+ // inplace at data.index of data.files:
+ saveImage: function (data, options) {
+ if (!data.canvas || options.disabled) {
+ return data;
+ }
+ var that = this,
+ file = data.files[data.index],
+ // eslint-disable-next-line new-cap
+ dfd = $.Deferred();
+ if (data.canvas.toBlob) {
+ data.canvas.toBlob(
+ function (blob) {
+ if (!blob.name) {
+ if (file.type === blob.type) {
+ blob.name = file.name;
+ } else if (file.name) {
+ blob.name = file.name.replace(
+ /\.\w+$/,
+ '.' + blob.type.substr(6)
+ );
+ }
+ }
+ // Don't restore invalid meta data:
+ if (file.type !== blob.type) {
+ delete data.imageHead;
+ }
+ // Store the created blob at the position
+ // of the original file in the files list:
+ data.files[data.index] = blob;
+ dfd.resolveWith(that, [data]);
+ },
+ options.type || file.type,
+ options.quality
+ );
+ } else {
+ return data;
+ }
+ return dfd.promise();
+ },
+
+ loadImageMetaData: function (data, options) {
+ if (options.disabled) {
+ return data;
+ }
+ var that = this,
+ // eslint-disable-next-line new-cap
+ dfd = $.Deferred();
+ loadImage.parseMetaData(
+ data.files[data.index],
+ function (result) {
+ $.extend(data, result);
+ dfd.resolveWith(that, [data]);
+ },
+ options
+ );
+ return dfd.promise();
+ },
+
+ saveImageMetaData: function (data, options) {
+ if (
+ !(
+ data.imageHead &&
+ data.canvas &&
+ data.canvas.toBlob &&
+ !options.disabled
+ )
+ ) {
+ return data;
+ }
+ var that = this,
+ file = data.files[data.index],
+ // eslint-disable-next-line new-cap
+ dfd = $.Deferred();
+ if (data.orientation === true && data.exifOffsets) {
+ // Reset Exif Orientation data:
+ loadImage.writeExifData(data.imageHead, data, 'Orientation', 1);
+ }
+ loadImage.replaceHead(file, data.imageHead, function (blob) {
+ blob.name = file.name;
+ data.files[data.index] = blob;
+ dfd.resolveWith(that, [data]);
+ });
+ return dfd.promise();
+ },
+
+ // Sets the resized version of the image as a property of the
+ // file object, must be called after "saveImage":
+ setImage: function (data, options) {
+ if (data.preview && !options.disabled) {
+ data.files[data.index][options.name || 'preview'] = data.preview;
+ }
+ return data;
+ },
+
+ deleteImageReferences: function (data, options) {
+ if (!options.disabled) {
+ delete data.img;
+ delete data.canvas;
+ delete data.preview;
+ delete data.imageHead;
+ }
+ return data;
+ }
+ }
+ });
+});
diff --git a/lib/web/jquery/fileUploader/jquery.fileupload-process.js b/lib/web/jquery/fileUploader/jquery.fileupload-process.js
new file mode 100644
index 0000000000000..a2f1009e508d1
--- /dev/null
+++ b/lib/web/jquery/fileUploader/jquery.fileupload-process.js
@@ -0,0 +1,170 @@
+/*
+ * jQuery File Upload Processing Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2012, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, require */
+
+(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery', 'jquery/fileUploader/jquery.fileupload'], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(require('jquery'), require('jquery/fileUploader/jquery.fileupload'));
+ } else {
+ // Browser globals:
+ factory(window.jQuery);
+ }
+})(function ($) {
+ 'use strict';
+
+ var originalAdd = $.blueimp.fileupload.prototype.options.add;
+
+ // The File Upload Processing plugin extends the fileupload widget
+ // with file processing functionality:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+ options: {
+ // The list of processing actions:
+ processQueue: [
+ /*
+ {
+ action: 'log',
+ type: 'debug'
+ }
+ */
+ ],
+ add: function (e, data) {
+ var $this = $(this);
+ data.process(function () {
+ return $this.fileupload('process', data);
+ });
+ originalAdd.call(this, e, data);
+ }
+ },
+
+ processActions: {
+ /*
+ log: function (data, options) {
+ console[options.type](
+ 'Processing "' + data.files[data.index].name + '"'
+ );
+ }
+ */
+ },
+
+ _processFile: function (data, originalData) {
+ var that = this,
+ // eslint-disable-next-line new-cap
+ dfd = $.Deferred().resolveWith(that, [data]),
+ chain = dfd.promise();
+ this._trigger('process', null, data);
+ $.each(data.processQueue, function (i, settings) {
+ var func = function (data) {
+ if (originalData.errorThrown) {
+ // eslint-disable-next-line new-cap
+ return $.Deferred().rejectWith(that, [originalData]).promise();
+ }
+ return that.processActions[settings.action].call(
+ that,
+ data,
+ settings
+ );
+ };
+ chain = chain[that._promisePipe](func, settings.always && func);
+ });
+ chain
+ .done(function () {
+ that._trigger('processdone', null, data);
+ that._trigger('processalways', null, data);
+ })
+ .fail(function () {
+ that._trigger('processfail', null, data);
+ that._trigger('processalways', null, data);
+ });
+ return chain;
+ },
+
+ // Replaces the settings of each processQueue item that
+ // are strings starting with an "@", using the remaining
+ // substring as key for the option map,
+ // e.g. "@autoUpload" is replaced with options.autoUpload:
+ _transformProcessQueue: function (options) {
+ var processQueue = [];
+ $.each(options.processQueue, function () {
+ var settings = {},
+ action = this.action,
+ prefix = this.prefix === true ? action : this.prefix;
+ $.each(this, function (key, value) {
+ if ($.type(value) === 'string' && value.charAt(0) === '@') {
+ settings[key] =
+ options[
+ value.slice(1) ||
+ (prefix
+ ? prefix + key.charAt(0).toUpperCase() + key.slice(1)
+ : key)
+ ];
+ } else {
+ settings[key] = value;
+ }
+ });
+ processQueue.push(settings);
+ });
+ options.processQueue = processQueue;
+ },
+
+ // Returns the number of files currently in the processsing queue:
+ processing: function () {
+ return this._processing;
+ },
+
+ // Processes the files given as files property of the data parameter,
+ // returns a Promise object that allows to bind callbacks:
+ process: function (data) {
+ var that = this,
+ options = $.extend({}, this.options, data);
+ if (options.processQueue && options.processQueue.length) {
+ this._transformProcessQueue(options);
+ if (this._processing === 0) {
+ this._trigger('processstart');
+ }
+ $.each(data.files, function (index) {
+ var opts = index ? $.extend({}, options) : options,
+ func = function () {
+ if (data.errorThrown) {
+ // eslint-disable-next-line new-cap
+ return $.Deferred().rejectWith(that, [data]).promise();
+ }
+ return that._processFile(opts, data);
+ };
+ opts.index = index;
+ that._processing += 1;
+ that._processingQueue = that._processingQueue[that._promisePipe](
+ func,
+ func
+ ).always(function () {
+ that._processing -= 1;
+ if (that._processing === 0) {
+ that._trigger('processstop');
+ }
+ });
+ });
+ }
+ return this._processingQueue;
+ },
+
+ _create: function () {
+ this._super();
+ this._processing = 0;
+ // eslint-disable-next-line new-cap
+ this._processingQueue = $.Deferred().resolveWith(this).promise();
+ }
+ });
+});
diff --git a/lib/web/jquery/fileUploader/jquery.fileupload-ui.js b/lib/web/jquery/fileUploader/jquery.fileupload-ui.js
index 8dca3ce992671..a4665f8392fe0 100644
--- a/lib/web/jquery/fileUploader/jquery.fileupload-ui.js
+++ b/lib/web/jquery/fileUploader/jquery.fileupload-ui.js
@@ -1,745 +1,759 @@
/*
- * jQuery File Upload User Interface Plugin 6.9.5
+ * jQuery File Upload User Interface Plugin
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2010, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
- * http://www.opensource.org/licenses/MIT
+ * https://opensource.org/licenses/MIT
*/
-/*jslint nomen: true, unparam: true, regexp: true */
-/*global define, window, document, URL, webkitURL, FileReader */
+/* global define, require */
(function (factory) {
- 'use strict';
- if (typeof define === 'function' && define.amd) {
- // Register as an anonymous AMD module:
- define([
- 'jquery',
- 'mage/template',
- 'jquery/fileUploader/load-image',
- 'jquery/fileUploader/jquery.fileupload-fp',
- 'jquery/fileUploader/jquery.iframe-transport'
- ], factory);
- } else {
- // Browser globals:
- factory(
- window.jQuery,
- window.mageTemplate,
- window.loadImage
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ 'jquery/fileUploader/vendor/blueimp-tmpl/js/tmpl',
+ 'jquery/fileUploader/jquery.fileupload-image',
+ 'jquery/fileUploader/jquery.fileupload-audio',
+ 'jquery/fileUploader/jquery.fileupload-video',
+ 'jquery/fileUploader/jquery.fileupload-validate'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('jquery/fileUploader/vendor/blueimp-tmpl/js/tmpl'),
+ require('jquery/fileUploader/jquery.fileupload-image'),
+ require('jquery/fileUploader/jquery.fileupload-audio'),
+ require('jquery/fileUploader/jquery.fileupload-video'),
+ require('jquery/fileUploader/jquery.fileupload-validate')
+ );
+ } else {
+ // Browser globals:
+ factory(window.jQuery, window.tmpl);
+ }
+})(function ($, tmpl) {
+ 'use strict';
+
+ $.blueimp.fileupload.prototype._specialOptions.push(
+ 'filesContainer',
+ 'uploadTemplateId',
+ 'downloadTemplateId'
+ );
+
+ // The UI version extends the file upload widget
+ // and adds complete user interface interaction:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+ options: {
+ // By default, files added to the widget are uploaded as soon
+ // as the user clicks on the start buttons. To enable automatic
+ // uploads, set the following option to true:
+ autoUpload: false,
+ // The class to show/hide UI elements:
+ showElementClass: 'in',
+ // The ID of the upload template:
+ uploadTemplateId: 'template-upload',
+ // The ID of the download template:
+ downloadTemplateId: 'template-download',
+ // The container for the list of files. If undefined, it is set to
+ // an element with class "files" inside of the widget element:
+ filesContainer: undefined,
+ // By default, files are appended to the files container.
+ // Set the following option to true, to prepend files instead:
+ prependFiles: false,
+ // The expected data type of the upload response, sets the dataType
+ // option of the $.ajax upload requests:
+ dataType: 'json',
+
+ // Error and info messages:
+ messages: {
+ unknownError: 'Unknown error'
+ },
+
+ // Function returning the current number of files,
+ // used by the maxNumberOfFiles validation:
+ getNumberOfFiles: function () {
+ return this.filesContainer.children().not('.processing').length;
+ },
+
+ // Callback to retrieve the list of files from the server response:
+ getFilesFromResponse: function (data) {
+ if (data.result && $.isArray(data.result.files)) {
+ return data.result.files;
+ }
+ return [];
+ },
+
+ // The add callback is invoked as soon as files are added to the fileupload
+ // widget (via file input selection, drag & drop or add API call).
+ // See the basic file upload widget for more information:
+ add: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var $this = $(this),
+ that = $this.data('blueimp-fileupload') || $this.data('fileupload'),
+ options = that.options;
+ data.context = that
+ ._renderUpload(data.files)
+ .data('data', data)
+ .addClass('processing');
+ options.filesContainer[options.prependFiles ? 'prepend' : 'append'](
+ data.context
);
- }
-}(function ($, tmpl, loadImage) {
- 'use strict';
-
- // The UI version extends the FP (file processing) version or the basic
- // file upload widget and adds complete user interface interaction:
- var parentWidget = ($.blueimpFP || $.blueimp).fileupload;
- $.widget('blueimpUI.fileupload', parentWidget, {
-
- options: {
- // By default, files added to the widget are uploaded as soon
- // as the user clicks on the start buttons. To enable automatic
- // uploads, set the following option to true:
- autoUpload: false,
- // The following option limits the number of files that are
- // allowed to be uploaded using this widget:
- maxNumberOfFiles: undefined,
- // The maximum allowed file size:
- maxFileSize: undefined,
- // The minimum allowed file size:
- minFileSize: undefined,
- // The regular expression for allowed file types, matches
- // against either file type or file name:
- acceptFileTypes: /.+$/i,
- // The regular expression to define for which files a preview
- // image is shown, matched against the file type:
- previewSourceFileTypes: /^image\/(gif|jpeg|png)$/,
- // The maximum file size of images that are to be displayed as preview:
- previewSourceMaxFileSize: 5000000, // 5MB
- // The maximum width of the preview images:
- previewMaxWidth: 80,
- // The maximum height of the preview images:
- previewMaxHeight: 80,
- // By default, preview images are displayed as canvas elements
- // if supported by the browser. Set the following option to false
- // to always display preview images as img elements:
- previewAsCanvas: true,
- // The ID of the upload template:
- uploadTemplateId: 'template-upload',
- // The ID of the download template:
- downloadTemplateId: 'template-download',
- // The container for the list of files. If undefined, it is set to
- // an element with class "files" inside of the widget element:
- filesContainer: undefined,
- // By default, files are appended to the files container.
- // Set the following option to true, to prepend files instead:
- prependFiles: false,
- // The expected data type of the upload response, sets the dataType
- // option of the $.ajax upload requests:
- dataType: 'json',
-
- // The add callback is invoked as soon as files are added to the fileupload
- // widget (via file input selection, drag & drop or add API call).
- // See the basic file upload widget for more information:
- add: function (e, data) {
- var that = $(this).data('fileupload'),
- options = that.options,
- files = data.files;
- $(this).fileupload('process', data).done(function () {
- that._adjustMaxNumberOfFiles(-files.length);
- data.maxNumberOfFilesAdjusted = true;
- data.files.valid = data.isValidated = that._validate(files);
- data.context = that._renderUpload(files).data('data', data);
- options.filesContainer[
- options.prependFiles ? 'prepend' : 'append'
- ](data.context);
- that._renderPreviews(files, data.context);
- that._forceReflow(data.context);
- that._transition(data.context).done(
- function () {
- if ((that._trigger('added', e, data) !== false) &&
- (options.autoUpload || data.autoUpload) &&
- data.autoUpload !== false && data.isValidated) {
- data.submit();
- }
- }
- );
- });
- },
- // Callback for the start of each file upload request:
- send: function (e, data) {
- var that = $(this).data('fileupload');
- if (!data.isValidated) {
- if (!data.maxNumberOfFilesAdjusted) {
- that._adjustMaxNumberOfFiles(-data.files.length);
- data.maxNumberOfFilesAdjusted = true;
- }
- if (!that._validate(data.files)) {
- return false;
- }
- }
- if (data.context && data.dataType &&
- data.dataType.substr(0, 6) === 'iframe') {
- // Iframe Transport does not support progress events.
- // In lack of an indeterminate progress bar, we set
- // the progress to 100%, showing the full animated bar:
- data.context
- .find('.progress').addClass(
- !$.support.transition && 'progress-animated'
- )
- .attr('aria-valuenow', 100)
- .find('.bar').css(
- 'width',
- '100%'
- );
- }
- return that._trigger('sent', e, data);
- },
- // Callback for successful uploads:
- done: function (e, data) {
- var that = $(this).data('fileupload'),
- template;
- if (data.context) {
- data.context.each(function (index) {
- var file = ($.isArray(data.result) &&
- data.result[index]) || {error: 'emptyResult'};
- if (file.error) {
- that._adjustMaxNumberOfFiles(1);
- }
- that._transition($(this)).done(
- function () {
- var node = $(this);
- template = that._renderDownload([file])
- .replaceAll(node);
- that._forceReflow(template);
- that._transition(template).done(
- function () {
- data.context = $(this);
- that._trigger('completed', e, data);
- }
- );
- }
- );
- });
- } else {
- if ($.isArray(data.result)) {
- $.each(data.result, function (index, file) {
- if (data.maxNumberOfFilesAdjusted && file.error) {
- that._adjustMaxNumberOfFiles(1);
- } else if (!data.maxNumberOfFilesAdjusted &&
- !file.error) {
- that._adjustMaxNumberOfFiles(-1);
- }
- });
- data.maxNumberOfFilesAdjusted = true;
- }
- template = that._renderDownload(data.result)
- .appendTo(that.options.filesContainer);
- that._forceReflow(template);
- that._transition(template).done(
- function () {
- data.context = $(this);
- that._trigger('completed', e, data);
- }
- );
- }
- },
- // Callback for failed (abort or error) uploads:
- fail: function (e, data) {
- var that = $(this).data('fileupload'),
- template;
- if (data.maxNumberOfFilesAdjusted) {
- that._adjustMaxNumberOfFiles(data.files.length);
- }
- if (data.context) {
- data.context.each(function (index) {
- if (data.errorThrown !== 'abort') {
- var file = data.files[index];
- file.error = file.error || data.errorThrown ||
- true;
- that._transition($(this)).done(
- function () {
- var node = $(this);
- template = that._renderDownload([file])
- .replaceAll(node);
- that._forceReflow(template);
- that._transition(template).done(
- function () {
- data.context = $(this);
- that._trigger('failed', e, data);
- }
- );
- }
- );
- } else {
- that._transition($(this)).done(
- function () {
- $(this).remove();
- that._trigger('failed', e, data);
- }
- );
- }
- });
- } else if (data.errorThrown !== 'abort') {
- data.context = that._renderUpload(data.files)
- .appendTo(that.options.filesContainer)
- .data('data', data);
- that._forceReflow(data.context);
- that._transition(data.context).done(
- function () {
- data.context = $(this);
- that._trigger('failed', e, data);
- }
- );
- } else {
- that._trigger('failed', e, data);
- }
- },
- // Callback for upload progress events:
- progress: function (e, data) {
- if (data.context) {
- var progress = parseInt(data.loaded / data.total * 100, 10);
- data.context.find('.progress')
- .attr('aria-valuenow', progress)
- .find('.bar').css(
- 'width',
- progress + '%'
- );
- }
- },
- // Callback for global upload progress events:
- progressall: function (e, data) {
- var $this = $(this),
- progress = parseInt(data.loaded / data.total * 100, 10),
- globalProgressNode = $this.find('.fileupload-progress'),
- extendedProgressNode = globalProgressNode
- .find('.progress-extended');
- if (extendedProgressNode.length) {
- extendedProgressNode.html(
- $this.data('fileupload')._renderExtendedProgress(data)
- );
- }
- globalProgressNode
- .find('.progress')
- .attr('aria-valuenow', progress)
- .find('.bar').css(
- 'width',
- progress + '%'
- );
- },
- // Callback for uploads start, equivalent to the global ajaxStart event:
- start: function (e) {
- var that = $(this).data('fileupload');
- that._transition($(this).find('.fileupload-progress')).done(
- function () {
- that._trigger('started', e);
- }
- );
- },
- // Callback for uploads stop, equivalent to the global ajaxStop event:
- stop: function (e) {
- var that = $(this).data('fileupload');
- that._transition($(this).find('.fileupload-progress')).done(
- function () {
- $(this).find('.progress')
- .attr('aria-valuenow', '0')
- .find('.bar').css('width', '0%');
- $(this).find('.progress-extended').html(' ');
- that._trigger('stopped', e);
- }
- );
- },
- // Callback for file deletion:
- destroy: function (e, data) {
- var that = $(this).data('fileupload');
- if (data.url) {
- $.ajax(data);
- that._adjustMaxNumberOfFiles(1);
- }
- that._transition(data.context).done(
- function () {
- $(this).remove();
- that._trigger('destroyed', e, data);
- }
- );
- }
- },
-
- // Link handler, that allows to download files
- // by drag & drop of the links to the desktop:
- _enableDragToDesktop: function () {
- var link = $(this),
- url = link.prop('href'),
- name = link.prop('download'),
- type = 'application/octet-stream';
- link.bind('dragstart', function (e) {
- try {
- e.originalEvent.dataTransfer.setData(
- 'DownloadURL',
- [type, name, url].join(':')
- );
- } catch (err) {}
- });
- },
-
- _adjustMaxNumberOfFiles: function (operand) {
- if (typeof this.options.maxNumberOfFiles === 'number') {
- this.options.maxNumberOfFiles += operand;
- if (this.options.maxNumberOfFiles < 1) {
- this._disableFileInputButton();
- } else {
- this._enableFileInputButton();
- }
- }
- },
-
- _formatFileSize: function (bytes) {
- if (typeof bytes !== 'number') {
- return '';
- }
- if (bytes >= 1000000000) {
- return (bytes / 1000000000).toFixed(2) + ' GB';
- }
- if (bytes >= 1000000) {
- return (bytes / 1000000).toFixed(2) + ' MB';
- }
- return (bytes / 1000).toFixed(2) + ' KB';
- },
-
- _formatBitrate: function (bits) {
- if (typeof bits !== 'number') {
- return '';
+ that._forceReflow(data.context);
+ that._transition(data.context);
+ data
+ .process(function () {
+ return $this.fileupload('process', data);
+ })
+ .always(function () {
+ data.context
+ .each(function (index) {
+ $(this)
+ .find('.size')
+ .text(that._formatFileSize(data.files[index].size));
+ })
+ .removeClass('processing');
+ that._renderPreviews(data);
+ })
+ .done(function () {
+ data.context.find('.edit,.start').prop('disabled', false);
+ if (
+ that._trigger('added', e, data) !== false &&
+ (options.autoUpload || data.autoUpload) &&
+ data.autoUpload !== false
+ ) {
+ data.submit();
}
- if (bits >= 1000000000) {
- return (bits / 1000000000).toFixed(2) + ' Gbit/s';
- }
- if (bits >= 1000000) {
- return (bits / 1000000).toFixed(2) + ' Mbit/s';
- }
- if (bits >= 1000) {
- return (bits / 1000).toFixed(2) + ' kbit/s';
- }
- return bits + ' bit/s';
- },
-
- _formatTime: function (seconds) {
- var date = new Date(seconds * 1000),
- days = parseInt(seconds / 86400, 10);
- days = days ? days + 'd ' : '';
- return days +
- ('0' + date.getUTCHours()).slice(-2) + ':' +
- ('0' + date.getUTCMinutes()).slice(-2) + ':' +
- ('0' + date.getUTCSeconds()).slice(-2);
- },
-
- _formatPercentage: function (floatValue) {
- return (floatValue * 100).toFixed(2) + ' %';
- },
-
- _renderExtendedProgress: function (data) {
- return this._formatBitrate(data.bitrate) + ' | ' +
- this._formatTime(
- (data.total - data.loaded) * 8 / data.bitrate
- ) + ' | ' +
- this._formatPercentage(
- data.loaded / data.total
- ) + ' | ' +
- this._formatFileSize(data.loaded) + ' / ' +
- this._formatFileSize(data.total);
- },
-
- _hasError: function (file) {
- if (file.error) {
- return file.error;
- }
- // The number of added files is subtracted from
- // maxNumberOfFiles before validation, so we check if
- // maxNumberOfFiles is below 0 (instead of below 1):
- if (this.options.maxNumberOfFiles < 0) {
- return 'maxNumberOfFiles';
- }
- // Files are accepted if either the file type or the file name
- // matches against the acceptFileTypes regular expression, as
- // only browsers with support for the File API report the type:
- if (!(this.options.acceptFileTypes.test(file.type) ||
- this.options.acceptFileTypes.test(file.name))) {
- return 'acceptFileTypes';
- }
- if (this.options.maxFileSize &&
- file.size > this.options.maxFileSize) {
- return 'maxFileSize';
- }
- if (typeof file.size === 'number' &&
- file.size < this.options.minFileSize) {
- return 'minFileSize';
- }
- return null;
- },
-
- _validate: function (files) {
- var that = this,
- valid = !!files.length;
- $.each(files, function (index, file) {
- file.error = that._hasError(file);
- if (file.error) {
- valid = false;
+ })
+ .fail(function () {
+ if (data.files.error) {
+ data.context.each(function (index) {
+ var error = data.files[index].error;
+ if (error) {
+ $(this).find('.error').text(error);
}
- });
- return valid;
- },
-
- _renderTemplate: function (func, files) {
- if (!func) {
- return $();
+ });
}
- var result = func({
- files: files,
- formatFileSize: this._formatFileSize,
- options: this.options
- });
- if (result instanceof $) {
- return result;
- }
- return $(this.options.templatesContainer).html(result).children();
- },
-
- _renderPreview: function (file, node) {
- var that = this,
- options = this.options,
- dfd = $.Deferred();
- return ((loadImage && loadImage(
- file,
- function (img) {
- node.append(img);
- that._forceReflow(node);
- that._transition(node).done(function () {
- dfd.resolveWith(node);
- });
- if (!$.contains(document.body, node[0])) {
- // If the element is not part of the DOM,
- // transition events are not triggered,
- // so we have to resolve manually:
- dfd.resolveWith(node);
- }
- },
- {
- maxWidth: options.previewMaxWidth,
- maxHeight: options.previewMaxHeight,
- canvas: options.previewAsCanvas
- }
- )) || dfd.resolveWith(node)) && dfd;
- },
-
- _renderPreviews: function (files, nodes) {
- var that = this,
- options = this.options;
- nodes.find('.preview span').each(function (index, element) {
- var file = files[index];
- if (options.previewSourceFileTypes.test(file.type) &&
- ($.type(options.previewSourceMaxFileSize) !== 'number' ||
- file.size < options.previewSourceMaxFileSize)) {
- that._processingQueue = that._processingQueue.pipe(function () {
- var dfd = $.Deferred();
- that._renderPreview(file, $(element)).done(
- function () {
- dfd.resolveWith(that);
- }
- );
- return dfd.promise();
- });
- }
+ });
+ },
+ // Callback for the start of each file upload request:
+ send: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that =
+ $(this).data('blueimp-fileupload') || $(this).data('fileupload');
+ if (
+ data.context &&
+ data.dataType &&
+ data.dataType.substr(0, 6) === 'iframe'
+ ) {
+ // Iframe Transport does not support progress events.
+ // In lack of an indeterminate progress bar, we set
+ // the progress to 100%, showing the full animated bar:
+ data.context
+ .find('.progress')
+ .addClass(!$.support.transition && 'progress-animated')
+ .attr('aria-valuenow', 100)
+ .children()
+ .first()
+ .css('width', '100%');
+ }
+ return that._trigger('sent', e, data);
+ },
+ // Callback for successful uploads:
+ done: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that =
+ $(this).data('blueimp-fileupload') || $(this).data('fileupload'),
+ getFilesFromResponse =
+ data.getFilesFromResponse || that.options.getFilesFromResponse,
+ files = getFilesFromResponse(data),
+ template,
+ deferred;
+ if (data.context) {
+ data.context.each(function (index) {
+ var file = files[index] || { error: 'Empty file upload result' };
+ deferred = that._addFinishedDeferreds();
+ that._transition($(this)).done(function () {
+ var node = $(this);
+ template = that._renderDownload([file]).replaceAll(node);
+ that._forceReflow(template);
+ that._transition(template).done(function () {
+ data.context = $(this);
+ that._trigger('completed', e, data);
+ that._trigger('finished', e, data);
+ deferred.resolve();
+ });
});
- return this._processingQueue;
- },
-
- _renderUpload: function (files) {
- return this._renderTemplate(
- this.options.uploadTemplate,
- files
+ });
+ } else {
+ template = that
+ ._renderDownload(files)
+ [that.options.prependFiles ? 'prependTo' : 'appendTo'](
+ that.options.filesContainer
);
- },
-
- _renderDownload: function (files) {
- return this._renderTemplate(
- this.options.downloadTemplate,
- files
- ).find('a[download]').each(this._enableDragToDesktop).end();
- },
-
- _startHandler: function (e) {
- e.preventDefault();
- var button = $(this),
- template = button.closest('.template-upload'),
- data = template.data('data');
- if (data && data.submit && !data.jqXHR && data.submit()) {
- button.prop('disabled', true);
- }
- },
-
- _cancelHandler: function (e) {
- e.preventDefault();
- var template = $(this).closest('.template-upload'),
- data = template.data('data') || {};
- if (!data.jqXHR) {
- data.errorThrown = 'abort';
- e.data.fileupload._trigger('fail', e, data);
+ that._forceReflow(template);
+ deferred = that._addFinishedDeferreds();
+ that._transition(template).done(function () {
+ data.context = $(this);
+ that._trigger('completed', e, data);
+ that._trigger('finished', e, data);
+ deferred.resolve();
+ });
+ }
+ },
+ // Callback for failed (abort or error) uploads:
+ fail: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that =
+ $(this).data('blueimp-fileupload') || $(this).data('fileupload'),
+ template,
+ deferred;
+ if (data.context) {
+ data.context.each(function (index) {
+ if (data.errorThrown !== 'abort') {
+ var file = data.files[index];
+ file.error =
+ file.error || data.errorThrown || data.i18n('unknownError');
+ deferred = that._addFinishedDeferreds();
+ that._transition($(this)).done(function () {
+ var node = $(this);
+ template = that._renderDownload([file]).replaceAll(node);
+ that._forceReflow(template);
+ that._transition(template).done(function () {
+ data.context = $(this);
+ that._trigger('failed', e, data);
+ that._trigger('finished', e, data);
+ deferred.resolve();
+ });
+ });
} else {
- data.jqXHR.abort();
+ deferred = that._addFinishedDeferreds();
+ that._transition($(this)).done(function () {
+ $(this).remove();
+ that._trigger('failed', e, data);
+ that._trigger('finished', e, data);
+ deferred.resolve();
+ });
}
- },
-
- _deleteHandler: function (e) {
- e.preventDefault();
- var button = $(this);
- e.data.fileupload._trigger('destroy', e, {
- context: button.closest('.template-download'),
- url: button.attr('data-url'),
- type: button.attr('data-type') || 'DELETE',
- dataType: e.data.fileupload.options.dataType
+ });
+ } else if (data.errorThrown !== 'abort') {
+ data.context = that
+ ._renderUpload(data.files)
+ [that.options.prependFiles ? 'prependTo' : 'appendTo'](
+ that.options.filesContainer
+ )
+ .data('data', data);
+ that._forceReflow(data.context);
+ deferred = that._addFinishedDeferreds();
+ that._transition(data.context).done(function () {
+ data.context = $(this);
+ that._trigger('failed', e, data);
+ that._trigger('finished', e, data);
+ deferred.resolve();
+ });
+ } else {
+ that._trigger('failed', e, data);
+ that._trigger('finished', e, data);
+ that._addFinishedDeferreds().resolve();
+ }
+ },
+ // Callback for upload progress events:
+ progress: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var progress = Math.floor((data.loaded / data.total) * 100);
+ if (data.context) {
+ data.context.each(function () {
+ $(this)
+ .find('.progress')
+ .attr('aria-valuenow', progress)
+ .children()
+ .first()
+ .css('width', progress + '%');
+ });
+ }
+ },
+ // Callback for global upload progress events:
+ progressall: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var $this = $(this),
+ progress = Math.floor((data.loaded / data.total) * 100),
+ globalProgressNode = $this.find('.fileupload-progress'),
+ extendedProgressNode = globalProgressNode.find('.progress-extended');
+ if (extendedProgressNode.length) {
+ extendedProgressNode.html(
+ (
+ $this.data('blueimp-fileupload') || $this.data('fileupload')
+ )._renderExtendedProgress(data)
+ );
+ }
+ globalProgressNode
+ .find('.progress')
+ .attr('aria-valuenow', progress)
+ .children()
+ .first()
+ .css('width', progress + '%');
+ },
+ // Callback for uploads start, equivalent to the global ajaxStart event:
+ start: function (e) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that =
+ $(this).data('blueimp-fileupload') || $(this).data('fileupload');
+ that._resetFinishedDeferreds();
+ that
+ ._transition($(this).find('.fileupload-progress'))
+ .done(function () {
+ that._trigger('started', e);
+ });
+ },
+ // Callback for uploads stop, equivalent to the global ajaxStop event:
+ stop: function (e) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that =
+ $(this).data('blueimp-fileupload') || $(this).data('fileupload'),
+ deferred = that._addFinishedDeferreds();
+ $.when.apply($, that._getFinishedDeferreds()).done(function () {
+ that._trigger('stopped', e);
+ });
+ that
+ ._transition($(this).find('.fileupload-progress'))
+ .done(function () {
+ $(this)
+ .find('.progress')
+ .attr('aria-valuenow', '0')
+ .children()
+ .first()
+ .css('width', '0%');
+ $(this).find('.progress-extended').html(' ');
+ deferred.resolve();
+ });
+ },
+ processstart: function (e) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ $(this).addClass('fileupload-processing');
+ },
+ processstop: function (e) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ $(this).removeClass('fileupload-processing');
+ },
+ // Callback for file deletion:
+ destroy: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ var that =
+ $(this).data('blueimp-fileupload') || $(this).data('fileupload'),
+ removeNode = function () {
+ that._transition(data.context).done(function () {
+ $(this).remove();
+ that._trigger('destroyed', e, data);
});
- },
-
- _forceReflow: function (node) {
- return $.support.transition && node.length &&
- node[0].offsetWidth;
- },
-
- _transition: function (node) {
- var dfd = $.Deferred();
- if ($.support.transition && node.hasClass('fade')) {
- node.bind(
- $.support.transition.end,
- function (e) {
- // Make sure we don't respond to other transitions events
- // in the container element, e.g. from button elements:
- if (e.target === node[0]) {
- node.unbind($.support.transition.end);
- dfd.resolveWith(node);
- }
- }
- ).toggleClass('in');
- } else {
- node.toggleClass('in');
- dfd.resolveWith(node);
- }
- return dfd;
- },
-
- _initButtonBarEventHandlers: function () {
- var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'),
- filesList = this.options.filesContainer,
- ns = this.options.namespace;
- fileUploadButtonBar.find('.start')
- .bind('click.' + ns, function (e) {
- e.preventDefault();
- filesList.find('.start button').click();
- });
- fileUploadButtonBar.find('.cancel')
- .bind('click.' + ns, function (e) {
- e.preventDefault();
- filesList.find('.cancel button').click();
- });
- fileUploadButtonBar.find('.delete')
- .bind('click.' + ns, function (e) {
- e.preventDefault();
- filesList.find('.delete input:checked')
- .siblings('button').click();
- fileUploadButtonBar.find('.toggle')
- .prop('checked', false);
- });
- fileUploadButtonBar.find('.toggle')
- .bind('change.' + ns, function (e) {
- filesList.find('.delete input').prop(
- 'checked',
- $(this).is(':checked')
- );
- });
- },
-
- _destroyButtonBarEventHandlers: function () {
- this.element.find('.fileupload-buttonbar button')
- .unbind('click.' + this.options.namespace);
- this.element.find('.fileupload-buttonbar .toggle')
- .unbind('change.' + this.options.namespace);
- },
-
- _initEventHandlers: function () {
- parentWidget.prototype._initEventHandlers.call(this);
- var eventData = {fileupload: this};
- this.options.filesContainer
- .delegate(
- '.start button',
- 'click.' + this.options.namespace,
- eventData,
- this._startHandler
- )
- .delegate(
- '.cancel button',
- 'click.' + this.options.namespace,
- eventData,
- this._cancelHandler
- )
- .delegate(
- '.delete button',
- 'click.' + this.options.namespace,
- eventData,
- this._deleteHandler
- );
- this._initButtonBarEventHandlers();
- },
-
- _destroyEventHandlers: function () {
- var options = this.options;
- this._destroyButtonBarEventHandlers();
- options.filesContainer
- .undelegate('.start button', 'click.' + options.namespace)
- .undelegate('.cancel button', 'click.' + options.namespace)
- .undelegate('.delete button', 'click.' + options.namespace);
- parentWidget.prototype._destroyEventHandlers.call(this);
- },
-
- _enableFileInputButton: function () {
- this.element.find('.fileinput-button input')
- .prop('disabled', false)
- .parent().removeClass('disabled');
- },
-
- _disableFileInputButton: function () {
- this.element.find('.fileinput-button input')
- .prop('disabled', true)
- .parent().addClass('disabled');
- },
-
- _initTemplates: function () {
- var options = this.options;
- options.templatesContainer = document.createElement(
- options.filesContainer.prop('nodeName')
- );
- if (tmpl) {
- if (options.uploadTemplateId) {
- options.uploadTemplate = tmpl(options.uploadTemplateId);
- }
- if (options.downloadTemplateId) {
- options.downloadTemplate = tmpl(options.downloadTemplateId);
- }
- }
- },
-
- _initFilesContainer: function () {
- var options = this.options;
- if (options.filesContainer === undefined) {
- options.filesContainer = this.element.find('.files');
- } else if (!(options.filesContainer instanceof $)) {
- options.filesContainer = $(options.filesContainer);
- }
- },
-
- _stringToRegExp: function (str) {
- var parts = str.split('/'),
- modifiers = parts.pop();
- parts.shift();
- return new RegExp(parts.join('/'), modifiers);
- },
-
- _initRegExpOptions: function () {
- var options = this.options;
- if ($.type(options.acceptFileTypes) === 'string') {
- options.acceptFileTypes = this._stringToRegExp(
- options.acceptFileTypes
- );
- }
- if ($.type(options.previewSourceFileTypes) === 'string') {
- options.previewSourceFileTypes = this._stringToRegExp(
- options.previewSourceFileTypes
- );
- }
- },
-
- _initSpecialOptions: function () {
- parentWidget.prototype._initSpecialOptions.call(this);
- this._initFilesContainer();
- this._initTemplates();
- this._initRegExpOptions();
- },
-
- _create: function () {
- parentWidget.prototype._create.call(this);
- this._refreshOptionsList.push(
- 'filesContainer',
- 'uploadTemplateId',
- 'downloadTemplateId'
- );
- if (!$.blueimpFP) {
- this._processingQueue = $.Deferred().resolveWith(this).promise();
- this.process = function () {
- return this._processingQueue;
- };
- }
- },
-
- enable: function () {
- var wasDisabled = false;
- if (this.options.disabled) {
- wasDisabled = true;
- }
- parentWidget.prototype.enable.call(this);
- if (wasDisabled) {
- this.element.find('input, button').prop('disabled', false);
- this._enableFileInputButton();
- }
- },
-
- disable: function () {
- if (!this.options.disabled) {
- this.element.find('input, button').prop('disabled', true);
- this._disableFileInputButton();
+ };
+ if (data.url) {
+ data.dataType = data.dataType || that.options.dataType;
+ $.ajax(data)
+ .done(removeNode)
+ .fail(function () {
+ that._trigger('destroyfailed', e, data);
+ });
+ } else {
+ removeNode();
+ }
+ }
+ },
+
+ _resetFinishedDeferreds: function () {
+ this._finishedUploads = [];
+ },
+
+ _addFinishedDeferreds: function (deferred) {
+ // eslint-disable-next-line new-cap
+ var promise = deferred || $.Deferred();
+ this._finishedUploads.push(promise);
+ return promise;
+ },
+
+ _getFinishedDeferreds: function () {
+ return this._finishedUploads;
+ },
+
+ // Link handler, that allows to download files
+ // by drag & drop of the links to the desktop:
+ _enableDragToDesktop: function () {
+ var link = $(this),
+ url = link.prop('href'),
+ name = link.prop('download'),
+ type = 'application/octet-stream';
+ link.on('dragstart', function (e) {
+ try {
+ e.originalEvent.dataTransfer.setData(
+ 'DownloadURL',
+ [type, name, url].join(':')
+ );
+ } catch (ignore) {
+ // Ignore exceptions
+ }
+ });
+ },
+
+ _formatFileSize: function (bytes) {
+ if (typeof bytes !== 'number') {
+ return '';
+ }
+ if (bytes >= 1000000000) {
+ return (bytes / 1000000000).toFixed(2) + ' GB';
+ }
+ if (bytes >= 1000000) {
+ return (bytes / 1000000).toFixed(2) + ' MB';
+ }
+ return (bytes / 1000).toFixed(2) + ' KB';
+ },
+
+ _formatBitrate: function (bits) {
+ if (typeof bits !== 'number') {
+ return '';
+ }
+ if (bits >= 1000000000) {
+ return (bits / 1000000000).toFixed(2) + ' Gbit/s';
+ }
+ if (bits >= 1000000) {
+ return (bits / 1000000).toFixed(2) + ' Mbit/s';
+ }
+ if (bits >= 1000) {
+ return (bits / 1000).toFixed(2) + ' kbit/s';
+ }
+ return bits.toFixed(2) + ' bit/s';
+ },
+
+ _formatTime: function (seconds) {
+ var date = new Date(seconds * 1000),
+ days = Math.floor(seconds / 86400);
+ days = days ? days + 'd ' : '';
+ return (
+ days +
+ ('0' + date.getUTCHours()).slice(-2) +
+ ':' +
+ ('0' + date.getUTCMinutes()).slice(-2) +
+ ':' +
+ ('0' + date.getUTCSeconds()).slice(-2)
+ );
+ },
+
+ _formatPercentage: function (floatValue) {
+ return (floatValue * 100).toFixed(2) + ' %';
+ },
+
+ _renderExtendedProgress: function (data) {
+ return (
+ this._formatBitrate(data.bitrate) +
+ ' | ' +
+ this._formatTime(((data.total - data.loaded) * 8) / data.bitrate) +
+ ' | ' +
+ this._formatPercentage(data.loaded / data.total) +
+ ' | ' +
+ this._formatFileSize(data.loaded) +
+ ' / ' +
+ this._formatFileSize(data.total)
+ );
+ },
+
+ _renderTemplate: function (func, files) {
+ if (!func) {
+ return $();
+ }
+ var result = func({
+ files: files,
+ formatFileSize: this._formatFileSize,
+ options: this.options
+ });
+ if (result instanceof $) {
+ return result;
+ }
+ return $(this.options.templatesContainer).html(result).children();
+ },
+
+ _renderPreviews: function (data) {
+ data.context.find('.preview').each(function (index, elm) {
+ $(elm).empty().append(data.files[index].preview);
+ });
+ },
+
+ _renderUpload: function (files) {
+ return this._renderTemplate(this.options.uploadTemplate, files);
+ },
+
+ _renderDownload: function (files) {
+ return this._renderTemplate(this.options.downloadTemplate, files)
+ .find('a[download]')
+ .each(this._enableDragToDesktop)
+ .end();
+ },
+
+ _editHandler: function (e) {
+ e.preventDefault();
+ if (!this.options.edit) return;
+ var that = this,
+ button = $(e.currentTarget),
+ template = button.closest('.template-upload'),
+ data = template.data('data'),
+ index = button.data().index;
+ this.options.edit(data.files[index]).then(function (file) {
+ if (!file) return;
+ data.files[index] = file;
+ data.context.addClass('processing');
+ template.find('.edit,.start').prop('disabled', true);
+ $(that.element)
+ .fileupload('process', data)
+ .always(function () {
+ template
+ .find('.size')
+ .text(that._formatFileSize(data.files[index].size));
+ data.context.removeClass('processing');
+ that._renderPreviews(data);
+ })
+ .done(function () {
+ template.find('.edit,.start').prop('disabled', false);
+ })
+ .fail(function () {
+ template.find('.edit').prop('disabled', false);
+ var error = data.files[index].error;
+ if (error) {
+ template.find('.error').text(error);
}
- parentWidget.prototype.disable.call(this);
+ });
+ });
+ },
+
+ _startHandler: function (e) {
+ e.preventDefault();
+ var button = $(e.currentTarget),
+ template = button.closest('.template-upload'),
+ data = template.data('data');
+ button.prop('disabled', true);
+ if (data && data.submit) {
+ data.submit();
+ }
+ },
+
+ _cancelHandler: function (e) {
+ e.preventDefault();
+ var template = $(e.currentTarget).closest(
+ '.template-upload,.template-download'
+ ),
+ data = template.data('data') || {};
+ data.context = data.context || template;
+ if (data.abort) {
+ data.abort();
+ } else {
+ data.errorThrown = 'abort';
+ this._trigger('fail', e, data);
+ }
+ },
+
+ _deleteHandler: function (e) {
+ e.preventDefault();
+ var button = $(e.currentTarget);
+ this._trigger(
+ 'destroy',
+ e,
+ $.extend(
+ {
+ context: button.closest('.template-download'),
+ type: 'DELETE'
+ },
+ button.data()
+ )
+ );
+ },
+
+ _forceReflow: function (node) {
+ return $.support.transition && node.length && node[0].offsetWidth;
+ },
+
+ _transition: function (node) {
+ // eslint-disable-next-line new-cap
+ var dfd = $.Deferred();
+ if (
+ $.support.transition &&
+ node.hasClass('fade') &&
+ node.is(':visible')
+ ) {
+ var transitionEndHandler = function (e) {
+ // Make sure we don't respond to other transition events
+ // in the container element, e.g. from button elements:
+ if (e.target === node[0]) {
+ node.off($.support.transition.end, transitionEndHandler);
+ dfd.resolveWith(node);
+ }
+ };
+ node
+ .on($.support.transition.end, transitionEndHandler)
+ .toggleClass(this.options.showElementClass);
+ } else {
+ node.toggleClass(this.options.showElementClass);
+ dfd.resolveWith(node);
+ }
+ return dfd;
+ },
+
+ _initButtonBarEventHandlers: function () {
+ var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'),
+ filesList = this.options.filesContainer;
+ this._on(fileUploadButtonBar.find('.start'), {
+ click: function (e) {
+ e.preventDefault();
+ filesList.find('.start').trigger('click');
}
-
- });
-
-}));
+ });
+ this._on(fileUploadButtonBar.find('.cancel'), {
+ click: function (e) {
+ e.preventDefault();
+ filesList.find('.cancel').trigger('click');
+ }
+ });
+ this._on(fileUploadButtonBar.find('.delete'), {
+ click: function (e) {
+ e.preventDefault();
+ filesList
+ .find('.toggle:checked')
+ .closest('.template-download')
+ .find('.delete')
+ .trigger('click');
+ fileUploadButtonBar.find('.toggle').prop('checked', false);
+ }
+ });
+ this._on(fileUploadButtonBar.find('.toggle'), {
+ change: function (e) {
+ filesList
+ .find('.toggle')
+ .prop('checked', $(e.currentTarget).is(':checked'));
+ }
+ });
+ },
+
+ _destroyButtonBarEventHandlers: function () {
+ this._off(
+ this.element
+ .find('.fileupload-buttonbar')
+ .find('.start, .cancel, .delete'),
+ 'click'
+ );
+ this._off(this.element.find('.fileupload-buttonbar .toggle'), 'change.');
+ },
+
+ _initEventHandlers: function () {
+ this._super();
+ this._on(this.options.filesContainer, {
+ 'click .edit': this._editHandler,
+ 'click .start': this._startHandler,
+ 'click .cancel': this._cancelHandler,
+ 'click .delete': this._deleteHandler
+ });
+ this._initButtonBarEventHandlers();
+ },
+
+ _destroyEventHandlers: function () {
+ this._destroyButtonBarEventHandlers();
+ this._off(this.options.filesContainer, 'click');
+ this._super();
+ },
+
+ _enableFileInputButton: function () {
+ this.element
+ .find('.fileinput-button input')
+ .prop('disabled', false)
+ .parent()
+ .removeClass('disabled');
+ },
+
+ _disableFileInputButton: function () {
+ this.element
+ .find('.fileinput-button input')
+ .prop('disabled', true)
+ .parent()
+ .addClass('disabled');
+ },
+
+ _initTemplates: function () {
+ var options = this.options;
+ options.templatesContainer = this.document[0].createElement(
+ options.filesContainer.prop('nodeName')
+ );
+ if (tmpl) {
+ if (options.uploadTemplateId) {
+ options.uploadTemplate = tmpl(options.uploadTemplateId);
+ }
+ if (options.downloadTemplateId) {
+ options.downloadTemplate = tmpl(options.downloadTemplateId);
+ }
+ }
+ },
+
+ _initFilesContainer: function () {
+ var options = this.options;
+ if (options.filesContainer === undefined) {
+ options.filesContainer = this.element.find('.files');
+ } else if (!(options.filesContainer instanceof $)) {
+ options.filesContainer = $(options.filesContainer);
+ }
+ },
+
+ _initSpecialOptions: function () {
+ this._super();
+ this._initFilesContainer();
+ // this._initTemplates();
+ },
+
+ _create: function () {
+ this._super();
+ this._resetFinishedDeferreds();
+ if (!$.support.fileInput) {
+ this._disableFileInputButton();
+ }
+ },
+
+ enable: function () {
+ var wasDisabled = false;
+ if (this.options.disabled) {
+ wasDisabled = true;
+ }
+ this._super();
+ if (wasDisabled) {
+ this.element.find('input, button').prop('disabled', false);
+ this._enableFileInputButton();
+ }
+ },
+
+ disable: function () {
+ if (!this.options.disabled) {
+ this.element.find('input, button').prop('disabled', true);
+ this._disableFileInputButton();
+ }
+ this._super();
+ }
+ });
+});
diff --git a/lib/web/jquery/fileUploader/jquery.fileupload-validate.js b/lib/web/jquery/fileUploader/jquery.fileupload-validate.js
new file mode 100644
index 0000000000000..d23e0f4a24ef7
--- /dev/null
+++ b/lib/web/jquery/fileUploader/jquery.fileupload-validate.js
@@ -0,0 +1,119 @@
+/*
+ * jQuery File Upload Validation Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, require */
+
+(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery', 'jquery/fileUploader/jquery.fileupload-process'], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(require('jquery'), require('jquery/fileUploader/jquery.fileupload-process'));
+ } else {
+ // Browser globals:
+ factory(window.jQuery);
+ }
+})(function ($) {
+ 'use strict';
+
+ // Append to the default processQueue:
+ $.blueimp.fileupload.prototype.options.processQueue.push({
+ action: 'validate',
+ // Always trigger this action,
+ // even if the previous action was rejected:
+ always: true,
+ // Options taken from the global options map:
+ acceptFileTypes: '@',
+ maxFileSize: '@',
+ minFileSize: '@',
+ maxNumberOfFiles: '@',
+ disabled: '@disableValidation'
+ });
+
+ // The File Upload Validation plugin extends the fileupload widget
+ // with file validation functionality:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+ options: {
+ /*
+ // The regular expression for allowed file types, matches
+ // against either file type or file name:
+ acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
+ // The maximum allowed file size in bytes:
+ maxFileSize: 10000000, // 10 MB
+ // The minimum allowed file size in bytes:
+ minFileSize: undefined, // No minimal file size
+ // The limit of files to be uploaded:
+ maxNumberOfFiles: 10,
+ */
+
+ // Function returning the current number of files,
+ // has to be overridden for maxNumberOfFiles validation:
+ getNumberOfFiles: $.noop,
+
+ // Error and info messages:
+ messages: {
+ maxNumberOfFiles: 'Maximum number of files exceeded',
+ acceptFileTypes: 'File type not allowed',
+ maxFileSize: 'File is too large',
+ minFileSize: 'File is too small'
+ }
+ },
+
+ processActions: {
+ validate: function (data, options) {
+ if (options.disabled) {
+ return data;
+ }
+ // eslint-disable-next-line new-cap
+ var dfd = $.Deferred(),
+ settings = this.options,
+ file = data.files[data.index],
+ fileSize;
+ if (options.minFileSize || options.maxFileSize) {
+ fileSize = file.size;
+ }
+ if (
+ $.type(options.maxNumberOfFiles) === 'number' &&
+ (settings.getNumberOfFiles() || 0) + data.files.length >
+ options.maxNumberOfFiles
+ ) {
+ file.error = settings.i18n('maxNumberOfFiles');
+ } else if (
+ options.acceptFileTypes &&
+ !(
+ options.acceptFileTypes.test(file.type) ||
+ options.acceptFileTypes.test(file.name)
+ )
+ ) {
+ file.error = settings.i18n('acceptFileTypes');
+ } else if (fileSize > options.maxFileSize) {
+ file.error = settings.i18n('maxFileSize');
+ } else if (
+ $.type(fileSize) === 'number' &&
+ fileSize < options.minFileSize
+ ) {
+ file.error = settings.i18n('minFileSize');
+ } else {
+ delete file.error;
+ }
+ if (file.error || data.files.error) {
+ data.files.error = true;
+ dfd.rejectWith(this, [data]);
+ } else {
+ dfd.resolveWith(this, [data]);
+ }
+ return dfd.promise();
+ }
+ }
+ });
+});
diff --git a/lib/web/jquery/fileUploader/jquery.fileupload-video.js b/lib/web/jquery/fileUploader/jquery.fileupload-video.js
new file mode 100644
index 0000000000000..bf247f38280a5
--- /dev/null
+++ b/lib/web/jquery/fileUploader/jquery.fileupload-video.js
@@ -0,0 +1,101 @@
+/*
+ * jQuery File Upload Video Preview Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, require */
+
+(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery', 'jquery/fileUploader/vendor/blueimp-load-image/js/load-image', 'jquery/fileUploader/jquery.fileupload-process'], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image'),
+ require('jquery/fileUploader/jquery.fileupload-process')
+ );
+ } else {
+ // Browser globals:
+ factory(window.jQuery, window.loadImage);
+ }
+})(function ($, loadImage) {
+ 'use strict';
+
+ // Prepend to the default processQueue:
+ $.blueimp.fileupload.prototype.options.processQueue.unshift(
+ {
+ action: 'loadVideo',
+ // Use the action as prefix for the "@" options:
+ prefix: true,
+ fileTypes: '@',
+ maxFileSize: '@',
+ disabled: '@disableVideoPreview'
+ },
+ {
+ action: 'setVideo',
+ name: '@videoPreviewName',
+ disabled: '@disableVideoPreview'
+ }
+ );
+
+ // The File Upload Video Preview plugin extends the fileupload widget
+ // with video preview functionality:
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+ options: {
+ // The regular expression for the types of video files to load,
+ // matched against the file type:
+ loadVideoFileTypes: /^video\/.*$/
+ },
+
+ _videoElement: document.createElement('video'),
+
+ processActions: {
+ // Loads the video file given via data.files and data.index
+ // as video element if the browser supports playing it.
+ // Accepts the options fileTypes (regular expression)
+ // and maxFileSize (integer) to limit the files to load:
+ loadVideo: function (data, options) {
+ if (options.disabled) {
+ return data;
+ }
+ var file = data.files[data.index],
+ url,
+ video;
+ if (
+ this._videoElement.canPlayType &&
+ this._videoElement.canPlayType(file.type) &&
+ ($.type(options.maxFileSize) !== 'number' ||
+ file.size <= options.maxFileSize) &&
+ (!options.fileTypes || options.fileTypes.test(file.type))
+ ) {
+ url = loadImage.createObjectURL(file);
+ if (url) {
+ video = this._videoElement.cloneNode(false);
+ video.src = url;
+ video.controls = true;
+ data.video = video;
+ return data;
+ }
+ }
+ return data;
+ },
+
+ // Sets the video element as a property of the file object:
+ setVideo: function (data, options) {
+ if (data.video && !options.disabled) {
+ data.files[data.index][options.name || 'preview'] = data.video;
+ }
+ return data;
+ }
+ }
+ });
+});
diff --git a/lib/web/jquery/fileUploader/jquery.fileupload.js b/lib/web/jquery/fileUploader/jquery.fileupload.js
index 676f8aa1e8058..8f0ff0d4faf03 100644
--- a/lib/web/jquery/fileUploader/jquery.fileupload.js
+++ b/lib/web/jquery/fileUploader/jquery.fileupload.js
@@ -1,1081 +1,1606 @@
/*
- * jQuery File Upload Plugin 5.16.4
+ * jQuery File Upload Plugin
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2010, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
- * http://www.opensource.org/licenses/MIT
+ * https://opensource.org/licenses/MIT
*/
-/*jslint nomen: true, unparam: true, regexp: true */
-/*global define, window, document, Blob, FormData, location */
+/* global define, require */
+/* eslint-disable new-cap */
(function (factory) {
- 'use strict';
- if (typeof define === 'function' && define.amd) {
- // Register as an anonymous AMD module:
- define([
- 'jquery',
- 'jquery-ui-modules/widget',
- 'jquery/fileUploader/jquery.iframe-transport'
- ], factory);
- } else {
- // Browser globals:
- factory(window.jQuery);
- }
-}(function ($) {
- 'use strict';
-
- // The FileReader API is not actually used, but works as feature detection,
- // as e.g. Safari supports XHR file uploads via the FormData API,
- // but not non-multipart XHR file uploads:
- $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader);
- $.support.xhrFormDataFileUpload = !!window.FormData;
-
- // The fileupload widget listens for change events on file input fields defined
- // via fileInput setting and paste or drop events of the given dropZone.
- // In addition to the default jQuery Widget methods, the fileupload widget
- // exposes the "add" and "send" methods, to add or directly send files using
- // the fileupload API.
- // By default, files added via file input selection, paste, drag & drop or
- // "add" method are uploaded immediately, but it is possible to override
- // the "add" callback option to queue file uploads.
- $.widget('blueimp.fileupload', {
-
- options: {
- // The namespace used for event handler binding on the dropZone and
- // fileInput collections.
- // If not set, the name of the widget ("fileupload") is used.
- namespace: undefined,
- // The drop target collection, by the default the complete document.
- // Set to null or an empty collection to disable drag & drop support:
- dropZone: $(document),
- // The file input field collection, that is listened for change events.
- // If undefined, it is set to the file input fields inside
- // of the widget element on plugin initialization.
- // Set to null or an empty collection to disable the change listener.
- fileInput: undefined,
- // By default, the file input field is replaced with a clone after
- // each input field change event. This is required for iframe transport
- // queues and allows change events to be fired for the same file
- // selection, but can be disabled by setting the following option to false:
- replaceFileInput: true,
- // The parameter name for the file form data (the request argument name).
- // If undefined or empty, the name property of the file input field is
- // used, or "files[]" if the file input name property is also empty,
- // can be a string or an array of strings:
- paramName: undefined,
- // By default, each file of a selection is uploaded using an individual
- // request for XHR type uploads. Set to false to upload file
- // selections in one request each:
- singleFileUploads: true,
- // To limit the number of files uploaded with one XHR request,
- // set the following option to an integer greater than 0:
- limitMultiFileUploads: undefined,
- // Set the following option to true to issue all file upload requests
- // in a sequential order:
- sequentialUploads: false,
- // To limit the number of concurrent uploads,
- // set the following option to an integer greater than 0:
- limitConcurrentUploads: undefined,
- // Set the following option to true to force iframe transport uploads:
- forceIframeTransport: false,
- // Set the following option to the location of a redirect url on the
- // origin server, for cross-domain iframe transport uploads:
- redirect: undefined,
- // The parameter name for the redirect url, sent as part of the form
- // data and set to 'redirect' if this option is empty:
- redirectParamName: undefined,
- // Set the following option to the location of a postMessage window,
- // to enable postMessage transport uploads:
- postMessage: undefined,
- // By default, XHR file uploads are sent as multipart/form-data.
- // The iframe transport is always using multipart/form-data.
- // Set to false to enable non-multipart XHR uploads:
- multipart: true,
- // To upload large files in smaller chunks, set the following option
- // to a preferred maximum chunk size. If set to 0, null or undefined,
- // or the browser does not support the required Blob API, files will
- // be uploaded as a whole.
- maxChunkSize: undefined,
- // When a non-multipart upload or a chunked multipart upload has been
- // aborted, this option can be used to resume the upload by setting
- // it to the size of the already uploaded bytes. This option is most
- // useful when modifying the options object inside of the "add" or
- // "send" callbacks, as the options are cloned for each file upload.
- uploadedBytes: undefined,
- // By default, failed (abort or error) file uploads are removed from the
- // global progress calculation. Set the following option to false to
- // prevent recalculating the global progress data:
- recalculateProgress: true,
- // Interval in milliseconds to calculate and trigger progress events:
- progressInterval: 100,
- // Interval in milliseconds to calculate progress bitrate:
- bitrateInterval: 500,
-
- // Additional form data to be sent along with the file uploads can be set
- // using this option, which accepts an array of objects with name and
- // value properties, a function returning such an array, a FormData
- // object (for XHR file uploads), or a simple object.
- // The form of the first fileInput is given as parameter to the function:
- formData: function (form) {
- return form.serializeArray();
- },
-
- // The add callback is invoked as soon as files are added to the fileupload
- // widget (via file input selection, drag & drop, paste or add API call).
- // If the singleFileUploads option is enabled, this callback will be
- // called once for each file in the selection for XHR file uplaods, else
- // once for each file selection.
- // The upload starts when the submit method is invoked on the data parameter.
- // The data object contains a files property holding the added files
- // and allows to override plugin options as well as define ajax settings.
- // Listeners for this callback can also be bound the following way:
- // .bind('fileuploadadd', func);
- // data.submit() returns a Promise object and allows to attach additional
- // handlers using jQuery's Deferred callbacks:
- // data.submit().done(func).fail(func).always(func);
- add: function (e, data) {
- data.submit();
- },
-
- // Other callbacks:
- // Callback for the submit event of each file upload:
- // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
- // Callback for the start of each file upload request:
- // send: function (e, data) {}, // .bind('fileuploadsend', func);
- // Callback for successful uploads:
- // done: function (e, data) {}, // .bind('fileuploaddone', func);
- // Callback for failed (abort or error) uploads:
- // fail: function (e, data) {}, // .bind('fileuploadfail', func);
- // Callback for completed (success, abort or error) requests:
- // always: function (e, data) {}, // .bind('fileuploadalways', func);
- // Callback for upload progress events:
- // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
- // Callback for global upload progress events:
- // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
- // Callback for uploads start, equivalent to the global ajaxStart event:
- // start: function (e) {}, // .bind('fileuploadstart', func);
- // Callback for uploads stop, equivalent to the global ajaxStop event:
- // stop: function (e) {}, // .bind('fileuploadstop', func);
- // Callback for change events of the fileInput collection:
- // change: function (e, data) {}, // .bind('fileuploadchange', func);
- // Callback for paste events to the dropZone collection:
- // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
- // Callback for drop events of the dropZone collection:
- // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
- // Callback for dragover events of the dropZone collection:
- // dragover: function (e) {}, // .bind('fileuploaddragover', func);
-
- // The plugin options are used as settings object for the ajax calls.
- // The following are jQuery ajax settings required for the file uploads:
- processData: false,
- contentType: false,
- cache: false
- },
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery', 'jquery/fileUploader/vendor/jquery.ui.widget'], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(require('jquery'), require('jquery/fileUploader/vendor/jquery.ui.widget'));
+ } else {
+ // Browser globals:
+ factory(window.jQuery);
+ }
+})(function ($) {
+ 'use strict';
- // A list of options that require a refresh after assigning a new value:
- _refreshOptionsList: [
- 'namespace',
- 'dropZone',
- 'fileInput',
- 'multipart',
- 'forceIframeTransport'
- ],
-
- _BitrateTimer: function () {
- this.timestamp = +(new Date());
- this.loaded = 0;
- this.bitrate = 0;
- this.getBitrate = function (now, loaded, interval) {
- var timeDiff = now - this.timestamp;
- if (!this.bitrate || !interval || timeDiff > interval) {
- this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
- this.loaded = loaded;
- this.timestamp = now;
- }
- return this.bitrate;
- };
- },
+ // Detect file input support, based on
+ // https://viljamis.com/2012/file-upload-support-on-mobile/
+ $.support.fileInput = !(
+ new RegExp(
+ // Handle devices which give false positives for the feature detection:
+ '(Android (1\\.[0156]|2\\.[01]))' +
+ '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
+ '|(w(eb)?OSBrowser)|(webOS)' +
+ '|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
+ ).test(window.navigator.userAgent) ||
+ // Feature detection for all other devices:
+ $('').prop('disabled')
+ );
- _isXHRUpload: function (options) {
- return !options.forceIframeTransport &&
- ((!options.multipart && $.support.xhrFileUpload) ||
- $.support.xhrFormDataFileUpload);
- },
+ // The FileReader API is not actually used, but works as feature detection,
+ // as some Safari versions (5?) support XHR file uploads via the FormData API,
+ // but not non-multipart XHR file uploads.
+ // window.XMLHttpRequestUpload is not available on IE10, so we check for
+ // window.ProgressEvent instead to detect XHR2 file upload capability:
+ $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader);
+ $.support.xhrFormDataFileUpload = !!window.FormData;
- _getFormData: function (options) {
- var formData;
- if (typeof options.formData === 'function') {
- return options.formData(options.form);
- }
- if ($.isArray(options.formData)) {
- return options.formData;
- }
- if (options.formData) {
- formData = [];
- $.each(options.formData, function (name, value) {
- formData.push({name: name, value: value});
- });
- return formData;
- }
- return [];
- },
+ // Detect support for Blob slicing (required for chunked uploads):
+ $.support.blobSlice =
+ window.Blob &&
+ (Blob.prototype.slice ||
+ Blob.prototype.webkitSlice ||
+ Blob.prototype.mozSlice);
- _getTotal: function (files) {
- var total = 0;
- $.each(files, function (index, file) {
- total += file.size || 1;
- });
- return total;
- },
+ /**
+ * Helper function to create drag handlers for dragover/dragenter/dragleave
+ *
+ * @param {string} type Event type
+ * @returns {Function} Drag handler
+ */
+ function getDragHandler(type) {
+ var isDragOver = type === 'dragover';
+ return function (e) {
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
+ var dataTransfer = e.dataTransfer;
+ if (
+ dataTransfer &&
+ $.inArray('Files', dataTransfer.types) !== -1 &&
+ this._trigger(type, $.Event(type, { delegatedEvent: e })) !== false
+ ) {
+ e.preventDefault();
+ if (isDragOver) {
+ dataTransfer.dropEffect = 'copy';
+ }
+ }
+ };
+ }
+
+ // The fileupload widget listens for change events on file input fields defined
+ // via fileInput setting and paste or drop events of the given dropZone.
+ // In addition to the default jQuery Widget methods, the fileupload widget
+ // exposes the "add" and "send" methods, to add or directly send files using
+ // the fileupload API.
+ // By default, files added via file input selection, paste, drag & drop or
+ // "add" method are uploaded immediately, but it is possible to override
+ // the "add" callback option to queue file uploads.
+ $.widget('blueimp.fileupload', {
+ options: {
+ // The drop target element(s), by the default the complete document.
+ // Set to null to disable drag & drop support:
+ dropZone: $(document),
+ // The paste target element(s), by the default undefined.
+ // Set to a DOM node or jQuery object to enable file pasting:
+ pasteZone: undefined,
+ // The file input field(s), that are listened to for change events.
+ // If undefined, it is set to the file input fields inside
+ // of the widget element on plugin initialization.
+ // Set to null to disable the change listener.
+ fileInput: undefined,
+ // By default, the file input field is replaced with a clone after
+ // each input field change event. This is required for iframe transport
+ // queues and allows change events to be fired for the same file
+ // selection, but can be disabled by setting the following option to false:
+ replaceFileInput: true,
+ // The parameter name for the file form data (the request argument name).
+ // If undefined or empty, the name property of the file input field is
+ // used, or "files[]" if the file input name property is also empty,
+ // can be a string or an array of strings:
+ paramName: undefined,
+ // By default, each file of a selection is uploaded using an individual
+ // request for XHR type uploads. Set to false to upload file
+ // selections in one request each:
+ singleFileUploads: true,
+ // To limit the number of files uploaded with one XHR request,
+ // set the following option to an integer greater than 0:
+ limitMultiFileUploads: undefined,
+ // The following option limits the number of files uploaded with one
+ // XHR request to keep the request size under or equal to the defined
+ // limit in bytes:
+ limitMultiFileUploadSize: undefined,
+ // Multipart file uploads add a number of bytes to each uploaded file,
+ // therefore the following option adds an overhead for each file used
+ // in the limitMultiFileUploadSize configuration:
+ limitMultiFileUploadSizeOverhead: 512,
+ // Set the following option to true to issue all file upload requests
+ // in a sequential order:
+ sequentialUploads: false,
+ // To limit the number of concurrent uploads,
+ // set the following option to an integer greater than 0:
+ limitConcurrentUploads: undefined,
+ // Set the following option to true to force iframe transport uploads:
+ forceIframeTransport: false,
+ // Set the following option to the location of a redirect url on the
+ // origin server, for cross-domain iframe transport uploads:
+ redirect: undefined,
+ // The parameter name for the redirect url, sent as part of the form
+ // data and set to 'redirect' if this option is empty:
+ redirectParamName: undefined,
+ // Set the following option to the location of a postMessage window,
+ // to enable postMessage transport uploads:
+ postMessage: undefined,
+ // By default, XHR file uploads are sent as multipart/form-data.
+ // The iframe transport is always using multipart/form-data.
+ // Set to false to enable non-multipart XHR uploads:
+ multipart: true,
+ // To upload large files in smaller chunks, set the following option
+ // to a preferred maximum chunk size. If set to 0, null or undefined,
+ // or the browser does not support the required Blob API, files will
+ // be uploaded as a whole.
+ maxChunkSize: undefined,
+ // When a non-multipart upload or a chunked multipart upload has been
+ // aborted, this option can be used to resume the upload by setting
+ // it to the size of the already uploaded bytes. This option is most
+ // useful when modifying the options object inside of the "add" or
+ // "send" callbacks, as the options are cloned for each file upload.
+ uploadedBytes: undefined,
+ // By default, failed (abort or error) file uploads are removed from the
+ // global progress calculation. Set the following option to false to
+ // prevent recalculating the global progress data:
+ recalculateProgress: true,
+ // Interval in milliseconds to calculate and trigger progress events:
+ progressInterval: 100,
+ // Interval in milliseconds to calculate progress bitrate:
+ bitrateInterval: 500,
+ // By default, uploads are started automatically when adding files:
+ autoUpload: true,
+ // By default, duplicate file names are expected to be handled on
+ // the server-side. If this is not possible (e.g. when uploading
+ // files directly to Amazon S3), the following option can be set to
+ // an empty object or an object mapping existing filenames, e.g.:
+ // { "image.jpg": true, "image (1).jpg": true }
+ // If it is set, all files will be uploaded with unique filenames,
+ // adding increasing number suffixes if necessary, e.g.:
+ // "image (2).jpg"
+ uniqueFilenames: undefined,
+
+ // Error and info messages:
+ messages: {
+ uploadedBytes: 'Uploaded bytes exceed file size'
+ },
+
+ // Translation function, gets the message key to be translated
+ // and an object with context specific data as arguments:
+ i18n: function (message, context) {
+ // eslint-disable-next-line no-param-reassign
+ message = this.messages[message] || message.toString();
+ if (context) {
+ $.each(context, function (key, value) {
+ // eslint-disable-next-line no-param-reassign
+ message = message.replace('{' + key + '}', value);
+ });
+ }
+ return message;
+ },
+
+ // Additional form data to be sent along with the file uploads can be set
+ // using this option, which accepts an array of objects with name and
+ // value properties, a function returning such an array, a FormData
+ // object (for XHR file uploads), or a simple object.
+ // The form of the first fileInput is given as parameter to the function:
+ formData: function (form) {
+ return form.serializeArray();
+ },
+
+ // The add callback is invoked as soon as files are added to the fileupload
+ // widget (via file input selection, drag & drop, paste or add API call).
+ // If the singleFileUploads option is enabled, this callback will be
+ // called once for each file in the selection for XHR file uploads, else
+ // once for each file selection.
+ //
+ // The upload starts when the submit method is invoked on the data parameter.
+ // The data object contains a files property holding the added files
+ // and allows you to override plugin options as well as define ajax settings.
+ //
+ // Listeners for this callback can also be bound the following way:
+ // .on('fileuploadadd', func);
+ //
+ // data.submit() returns a Promise object and allows to attach additional
+ // handlers using jQuery's Deferred callbacks:
+ // data.submit().done(func).fail(func).always(func);
+ add: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ if (
+ data.autoUpload ||
+ (data.autoUpload !== false &&
+ $(this).fileupload('option', 'autoUpload'))
+ ) {
+ data.process().done(function () {
+ data.submit();
+ });
+ }
+ },
+
+ // Other callbacks:
+
+ // Callback for the submit event of each file upload:
+ // submit: function (e, data) {}, // .on('fileuploadsubmit', func);
+
+ // Callback for the start of each file upload request:
+ // send: function (e, data) {}, // .on('fileuploadsend', func);
+
+ // Callback for successful uploads:
+ // done: function (e, data) {}, // .on('fileuploaddone', func);
+
+ // Callback for failed (abort or error) uploads:
+ // fail: function (e, data) {}, // .on('fileuploadfail', func);
+
+ // Callback for completed (success, abort or error) requests:
+ // always: function (e, data) {}, // .on('fileuploadalways', func);
- _onProgress: function (e, data) {
- if (e.lengthComputable) {
- var now = +(new Date()),
- total,
- loaded;
- if (data._time && data.progressInterval &&
- (now - data._time < data.progressInterval) &&
- e.loaded !== e.total) {
- return;
+ // Callback for upload progress events:
+ // progress: function (e, data) {}, // .on('fileuploadprogress', func);
+
+ // Callback for global upload progress events:
+ // progressall: function (e, data) {}, // .on('fileuploadprogressall', func);
+
+ // Callback for uploads start, equivalent to the global ajaxStart event:
+ // start: function (e) {}, // .on('fileuploadstart', func);
+
+ // Callback for uploads stop, equivalent to the global ajaxStop event:
+ // stop: function (e) {}, // .on('fileuploadstop', func);
+
+ // Callback for change events of the fileInput(s):
+ // change: function (e, data) {}, // .on('fileuploadchange', func);
+
+ // Callback for paste events to the pasteZone(s):
+ // paste: function (e, data) {}, // .on('fileuploadpaste', func);
+
+ // Callback for drop events of the dropZone(s):
+ // drop: function (e, data) {}, // .on('fileuploaddrop', func);
+
+ // Callback for dragover events of the dropZone(s):
+ // dragover: function (e) {}, // .on('fileuploaddragover', func);
+
+ // Callback before the start of each chunk upload request (before form data initialization):
+ // chunkbeforesend: function (e, data) {}, // .on('fileuploadchunkbeforesend', func);
+
+ // Callback for the start of each chunk upload request:
+ // chunksend: function (e, data) {}, // .on('fileuploadchunksend', func);
+
+ // Callback for successful chunk uploads:
+ // chunkdone: function (e, data) {}, // .on('fileuploadchunkdone', func);
+
+ // Callback for failed (abort or error) chunk uploads:
+ // chunkfail: function (e, data) {}, // .on('fileuploadchunkfail', func);
+
+ // Callback for completed (success, abort or error) chunk upload requests:
+ // chunkalways: function (e, data) {}, // .on('fileuploadchunkalways', func);
+
+ // The plugin options are used as settings object for the ajax calls.
+ // The following are jQuery ajax settings required for the file uploads:
+ processData: false,
+ contentType: false,
+ cache: false,
+ timeout: 0
+ },
+
+ // jQuery versions before 1.8 require promise.pipe if the return value is
+ // used, as promise.then in older versions has a different behavior, see:
+ // https://blog.jquery.com/2012/08/09/jquery-1-8-released/
+ // https://bugs.jquery.com/ticket/11010
+ // https://github.com/blueimp/jQuery-File-Upload/pull/3435
+ _promisePipe: (function () {
+ var parts = $.fn.jquery.split('.');
+ return Number(parts[0]) > 1 || Number(parts[1]) > 7 ? 'then' : 'pipe';
+ })(),
+
+ // A list of options that require reinitializing event listeners and/or
+ // special initialization code:
+ _specialOptions: [
+ 'fileInput',
+ 'dropZone',
+ 'pasteZone',
+ 'multipart',
+ 'forceIframeTransport'
+ ],
+
+ _blobSlice:
+ $.support.blobSlice &&
+ function () {
+ var slice = this.slice || this.webkitSlice || this.mozSlice;
+ return slice.apply(this, arguments);
+ },
+
+ _BitrateTimer: function () {
+ this.timestamp = Date.now ? Date.now() : new Date().getTime();
+ this.loaded = 0;
+ this.bitrate = 0;
+ this.getBitrate = function (now, loaded, interval) {
+ var timeDiff = now - this.timestamp;
+ if (!this.bitrate || !interval || timeDiff > interval) {
+ this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
+ this.loaded = loaded;
+ this.timestamp = now;
+ }
+ return this.bitrate;
+ };
+ },
+
+ _isXHRUpload: function (options) {
+ return (
+ !options.forceIframeTransport &&
+ ((!options.multipart && $.support.xhrFileUpload) ||
+ $.support.xhrFormDataFileUpload)
+ );
+ },
+
+ _getFormData: function (options) {
+ var formData;
+ if ($.type(options.formData) === 'function') {
+ return options.formData(options.form);
+ }
+ if ($.isArray(options.formData)) {
+ return options.formData;
+ }
+ if ($.type(options.formData) === 'object') {
+ formData = [];
+ $.each(options.formData, function (name, value) {
+ formData.push({ name: name, value: value });
+ });
+ return formData;
+ }
+ return [];
+ },
+
+ _getTotal: function (files) {
+ var total = 0;
+ $.each(files, function (index, file) {
+ total += file.size || 1;
+ });
+ return total;
+ },
+
+ _initProgressObject: function (obj) {
+ var progress = {
+ loaded: 0,
+ total: 0,
+ bitrate: 0
+ };
+ if (obj._progress) {
+ $.extend(obj._progress, progress);
+ } else {
+ obj._progress = progress;
+ }
+ },
+
+ _initResponseObject: function (obj) {
+ var prop;
+ if (obj._response) {
+ for (prop in obj._response) {
+ if (Object.prototype.hasOwnProperty.call(obj._response, prop)) {
+ delete obj._response[prop];
+ }
+ }
+ } else {
+ obj._response = {};
+ }
+ },
+
+ _onProgress: function (e, data) {
+ if (e.lengthComputable) {
+ var now = Date.now ? Date.now() : new Date().getTime(),
+ loaded;
+ if (
+ data._time &&
+ data.progressInterval &&
+ now - data._time < data.progressInterval &&
+ e.loaded !== e.total
+ ) {
+ return;
+ }
+ data._time = now;
+ loaded =
+ Math.floor(
+ (e.loaded / e.total) * (data.chunkSize || data._progress.total)
+ ) + (data.uploadedBytes || 0);
+ // Add the difference from the previously loaded state
+ // to the global loaded counter:
+ this._progress.loaded += loaded - data._progress.loaded;
+ this._progress.bitrate = this._bitrateTimer.getBitrate(
+ now,
+ this._progress.loaded,
+ data.bitrateInterval
+ );
+ data._progress.loaded = data.loaded = loaded;
+ data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
+ now,
+ loaded,
+ data.bitrateInterval
+ );
+ // Trigger a custom progress event with a total data property set
+ // to the file size(s) of the current upload and a loaded data
+ // property calculated accordingly:
+ this._trigger(
+ 'progress',
+ $.Event('progress', { delegatedEvent: e }),
+ data
+ );
+ // Trigger a global progress event for all current file uploads,
+ // including ajax calls queued for sequential file uploads:
+ this._trigger(
+ 'progressall',
+ $.Event('progressall', { delegatedEvent: e }),
+ this._progress
+ );
+ }
+ },
+
+ _initProgressListener: function (options) {
+ var that = this,
+ xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
+ // Accesss to the native XHR object is required to add event listeners
+ // for the upload progress event:
+ if (xhr.upload) {
+ $(xhr.upload).on('progress', function (e) {
+ var oe = e.originalEvent;
+ // Make sure the progress event properties get copied over:
+ e.lengthComputable = oe.lengthComputable;
+ e.loaded = oe.loaded;
+ e.total = oe.total;
+ that._onProgress(e, options);
+ });
+ options.xhr = function () {
+ return xhr;
+ };
+ }
+ },
+
+ _deinitProgressListener: function (options) {
+ var xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
+ if (xhr.upload) {
+ $(xhr.upload).off('progress');
+ }
+ },
+
+ _isInstanceOf: function (type, obj) {
+ // Cross-frame instanceof check
+ return Object.prototype.toString.call(obj) === '[object ' + type + ']';
+ },
+
+ _getUniqueFilename: function (name, map) {
+ // eslint-disable-next-line no-param-reassign
+ name = String(name);
+ if (map[name]) {
+ // eslint-disable-next-line no-param-reassign
+ name = name.replace(/(?: \(([\d]+)\))?(\.[^.]+)?$/, function (
+ _,
+ p1,
+ p2
+ ) {
+ var index = p1 ? Number(p1) + 1 : 1;
+ var ext = p2 || '';
+ return ' (' + index + ')' + ext;
+ });
+ return this._getUniqueFilename(name, map);
+ }
+ map[name] = true;
+ return name;
+ },
+
+ _initXHRData: function (options) {
+ var that = this,
+ formData,
+ file = options.files[0],
+ // Ignore non-multipart setting if not supported:
+ multipart = options.multipart || !$.support.xhrFileUpload,
+ paramName =
+ $.type(options.paramName) === 'array'
+ ? options.paramName[0]
+ : options.paramName;
+ options.headers = $.extend({}, options.headers);
+ if (options.contentRange) {
+ options.headers['Content-Range'] = options.contentRange;
+ }
+ if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
+ options.headers['Content-Disposition'] =
+ 'attachment; filename="' +
+ encodeURI(file.uploadName || file.name) +
+ '"';
+ }
+ if (!multipart) {
+ options.contentType = file.type || 'application/octet-stream';
+ options.data = options.blob || file;
+ } else if ($.support.xhrFormDataFileUpload) {
+ if (options.postMessage) {
+ // window.postMessage does not allow sending FormData
+ // objects, so we just add the File/Blob objects to
+ // the formData array and let the postMessage window
+ // create the FormData object out of this array:
+ formData = this._getFormData(options);
+ if (options.blob) {
+ formData.push({
+ name: paramName,
+ value: options.blob
+ });
+ } else {
+ $.each(options.files, function (index, file) {
+ formData.push({
+ name:
+ ($.type(options.paramName) === 'array' &&
+ options.paramName[index]) ||
+ paramName,
+ value: file
+ });
+ });
+ }
+ } else {
+ if (that._isInstanceOf('FormData', options.formData)) {
+ formData = options.formData;
+ } else {
+ formData = new FormData();
+ $.each(this._getFormData(options), function (index, field) {
+ formData.append(field.name, field.value);
+ });
+ }
+ if (options.blob) {
+ formData.append(
+ paramName,
+ options.blob,
+ file.uploadName || file.name
+ );
+ } else {
+ $.each(options.files, function (index, file) {
+ // This check allows the tests to run with
+ // dummy objects:
+ if (
+ that._isInstanceOf('File', file) ||
+ that._isInstanceOf('Blob', file)
+ ) {
+ var fileName = file.uploadName || file.name;
+ if (options.uniqueFilenames) {
+ fileName = that._getUniqueFilename(
+ fileName,
+ options.uniqueFilenames
+ );
}
- data._time = now;
- total = data.total || this._getTotal(data.files);
- loaded = parseInt(
- e.loaded / e.total * (data.chunkSize || total),
- 10
- ) + (data.uploadedBytes || 0);
- this._loaded += loaded - (data.loaded || data.uploadedBytes || 0);
- data.lengthComputable = true;
- data.loaded = loaded;
- data.total = total;
- data.bitrate = data._bitrateTimer.getBitrate(
- now,
- loaded,
- data.bitrateInterval
+ formData.append(
+ ($.type(options.paramName) === 'array' &&
+ options.paramName[index]) ||
+ paramName,
+ file,
+ fileName
);
- // Trigger a custom progress event with a total data property set
- // to the file size(s) of the current upload and a loaded data
- // property calculated accordingly:
- this._trigger('progress', e, data);
- // Trigger a global progress event for all current file uploads,
- // including ajax calls queued for sequential file uploads:
- this._trigger('progressall', e, {
- lengthComputable: true,
- loaded: this._loaded,
- total: this._total,
- bitrate: this._bitrateTimer.getBitrate(
- now,
- this._loaded,
- data.bitrateInterval
- )
- });
- }
- },
+ }
+ });
+ }
+ }
+ options.data = formData;
+ }
+ // Blob reference is not needed anymore, free memory:
+ options.blob = null;
+ },
- _initProgressListener: function (options) {
- var that = this,
- xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
- // Accesss to the native XHR object is required to add event listeners
- // for the upload progress event:
- if (xhr.upload) {
- $(xhr.upload).bind('progress', function (e) {
- var oe = e.originalEvent;
- // Make sure the progress event properties get copied over:
- e.lengthComputable = oe.lengthComputable;
- e.loaded = oe.loaded;
- e.total = oe.total;
- that._onProgress(e, options);
- });
- options.xhr = function () {
- return xhr;
- };
- }
- },
+ _initIframeSettings: function (options) {
+ var targetHost = $('').prop('href', options.url).prop('host');
+ // Setting the dataType to iframe enables the iframe transport:
+ options.dataType = 'iframe ' + (options.dataType || '');
+ // The iframe transport accepts a serialized array as form data:
+ options.formData = this._getFormData(options);
+ // Add redirect url to form data on cross-domain uploads:
+ if (options.redirect && targetHost && targetHost !== location.host) {
+ options.formData.push({
+ name: options.redirectParamName || 'redirect',
+ value: options.redirect
+ });
+ }
+ },
- _initXHRData: function (options) {
- var formData,
- file = options.files[0],
- // Ignore non-multipart setting if not supported:
- multipart = options.multipart || !$.support.xhrFileUpload,
- paramName = options.paramName[0];
- if (!multipart || options.blob) {
- // For non-multipart uploads and chunked uploads,
- // file meta data is not part of the request body,
- // so we transmit this data as part of the HTTP headers.
- // For cross domain requests, these headers must be allowed
- // via Access-Control-Allow-Headers or removed using
- // the beforeSend callback:
- options.headers = $.extend(options.headers, {
- 'X-File-Name': file.name,
- 'X-File-Type': file.type,
- 'X-File-Size': file.size
- });
- if (!options.blob) {
- // Non-chunked non-multipart upload:
- options.contentType = file.type;
- options.data = file;
- } else if (!multipart) {
- // Chunked non-multipart upload:
- options.contentType = 'application/octet-stream';
- options.data = options.blob;
- }
- }
- if (multipart && $.support.xhrFormDataFileUpload) {
- if (options.postMessage) {
- // window.postMessage does not allow sending FormData
- // objects, so we just add the File/Blob objects to
- // the formData array and let the postMessage window
- // create the FormData object out of this array:
- formData = this._getFormData(options);
- if (options.blob) {
- formData.push({
- name: paramName,
- value: options.blob
- });
- } else {
- $.each(options.files, function (index, file) {
- formData.push({
- name: options.paramName[index] || paramName,
- value: file
- });
- });
- }
- } else {
- if (options.formData instanceof FormData) {
- formData = options.formData;
- } else {
- formData = new FormData();
- $.each(this._getFormData(options), function (index, field) {
- formData.append(field.name, field.value);
- });
- }
- if (options.blob) {
- formData.append(paramName, options.blob, file.name);
- } else {
- $.each(options.files, function (index, file) {
- // File objects are also Blob instances.
- // This check allows the tests to run with
- // dummy objects:
- if (file instanceof Blob) {
- formData.append(
- options.paramName[index] || paramName,
- file,
- file.name
- );
- }
- });
- }
- }
- options.data = formData;
- }
- // Blob reference is not needed anymore, free memory:
- options.blob = null;
- },
+ _initDataSettings: function (options) {
+ if (this._isXHRUpload(options)) {
+ if (!this._chunkedUpload(options, true)) {
+ if (!options.data) {
+ this._initXHRData(options);
+ }
+ this._initProgressListener(options);
+ }
+ if (options.postMessage) {
+ // Setting the dataType to postmessage enables the
+ // postMessage transport:
+ options.dataType = 'postmessage ' + (options.dataType || '');
+ }
+ } else {
+ this._initIframeSettings(options);
+ }
+ },
- _initIframeSettings: function (options) {
- // Setting the dataType to iframe enables the iframe transport:
- options.dataType = 'iframe ' + (options.dataType || '');
- // The iframe transport accepts a serialized array as form data:
- options.formData = this._getFormData(options);
- // Add redirect url to form data on cross-domain uploads:
- if (options.redirect && $('').prop('href', options.url)
- .prop('host') !== location.host) {
- options.formData.push({
- name: options.redirectParamName || 'redirect',
- value: options.redirect
- });
- }
- },
+ _getParamName: function (options) {
+ var fileInput = $(options.fileInput),
+ paramName = options.paramName;
+ if (!paramName) {
+ paramName = [];
+ fileInput.each(function () {
+ var input = $(this),
+ name = input.prop('name') || 'files[]',
+ i = (input.prop('files') || [1]).length;
+ while (i) {
+ paramName.push(name);
+ i -= 1;
+ }
+ });
+ if (!paramName.length) {
+ paramName = [fileInput.prop('name') || 'files[]'];
+ }
+ } else if (!$.isArray(paramName)) {
+ paramName = [paramName];
+ }
+ return paramName;
+ },
- _initDataSettings: function (options) {
- if (this._isXHRUpload(options)) {
- if (!this._chunkedUpload(options, true)) {
- if (!options.data) {
- this._initXHRData(options);
- }
- this._initProgressListener(options);
- }
- if (options.postMessage) {
- // Setting the dataType to postmessage enables the
- // postMessage transport:
- options.dataType = 'postmessage ' + (options.dataType || '');
- }
- } else {
- this._initIframeSettings(options, 'iframe');
- }
- },
+ _initFormSettings: function (options) {
+ // Retrieve missing options from the input field and the
+ // associated form, if available:
+ if (!options.form || !options.form.length) {
+ options.form = $(options.fileInput.prop('form'));
+ // If the given file input doesn't have an associated form,
+ // use the default widget file input's form:
+ if (!options.form.length) {
+ options.form = $(this.options.fileInput.prop('form'));
+ }
+ }
+ options.paramName = this._getParamName(options);
+ if (!options.url) {
+ options.url = options.form.prop('action') || location.href;
+ }
+ // The HTTP request method must be "POST" or "PUT":
+ options.type = (
+ options.type ||
+ ($.type(options.form.prop('method')) === 'string' &&
+ options.form.prop('method')) ||
+ ''
+ ).toUpperCase();
+ if (
+ options.type !== 'POST' &&
+ options.type !== 'PUT' &&
+ options.type !== 'PATCH'
+ ) {
+ options.type = 'POST';
+ }
+ if (!options.formAcceptCharset) {
+ options.formAcceptCharset = options.form.attr('accept-charset');
+ }
+ },
- _getParamName: function (options) {
- var fileInput = $(options.fileInput),
- paramName = options.paramName;
- if (!paramName) {
- paramName = [];
- fileInput.each(function () {
- var input = $(this),
- name = input.prop('name') || 'files[]',
- i = (input.prop('files') || [1]).length;
- while (i) {
- paramName.push(name);
- i -= 1;
- }
- });
- if (!paramName.length) {
- paramName = [fileInput.prop('name') || 'files[]'];
- }
- } else if (!$.isArray(paramName)) {
- paramName = [paramName];
- }
- return paramName;
- },
+ _getAJAXSettings: function (data) {
+ var options = $.extend({}, this.options, data);
+ this._initFormSettings(options);
+ this._initDataSettings(options);
+ return options;
+ },
- _initFormSettings: function (options) {
- // Retrieve missing options from the input field and the
- // associated form, if available:
- if (!options.form || !options.form.length) {
- options.form = $(options.fileInput.prop('form'));
- }
- options.paramName = this._getParamName(options);
- if (!options.url) {
- options.url = options.form.prop('action') || location.href;
- }
- // The HTTP request method must be "POST" or "PUT":
- options.type = (options.type || options.form.prop('method') || '')
- .toUpperCase();
- if (options.type !== 'POST' && options.type !== 'PUT') {
- options.type = 'POST';
- }
- if (!options.formAcceptCharset) {
- options.formAcceptCharset = options.form.attr('accept-charset');
- }
- },
+ // jQuery 1.6 doesn't provide .state(),
+ // while jQuery 1.8+ removed .isRejected() and .isResolved():
+ _getDeferredState: function (deferred) {
+ if (deferred.state) {
+ return deferred.state();
+ }
+ if (deferred.isResolved()) {
+ return 'resolved';
+ }
+ if (deferred.isRejected()) {
+ return 'rejected';
+ }
+ return 'pending';
+ },
- _getAJAXSettings: function (data) {
- var options = $.extend({}, this.options, data);
- this._initFormSettings(options);
- this._initDataSettings(options);
- return options;
- },
+ // Maps jqXHR callbacks to the equivalent
+ // methods of the given Promise object:
+ _enhancePromise: function (promise) {
+ promise.success = promise.done;
+ promise.error = promise.fail;
+ promise.complete = promise.always;
+ return promise;
+ },
- // Maps jqXHR callbacks to the equivalent
- // methods of the given Promise object:
- _enhancePromise: function (promise) {
- promise.success = promise.done;
- promise.error = promise.fail;
- promise.complete = promise.always;
- return promise;
- },
+ // Creates and returns a Promise object enhanced with
+ // the jqXHR methods abort, success, error and complete:
+ _getXHRPromise: function (resolveOrReject, context, args) {
+ var dfd = $.Deferred(),
+ promise = dfd.promise();
+ // eslint-disable-next-line no-param-reassign
+ context = context || this.options.context || promise;
+ if (resolveOrReject === true) {
+ dfd.resolveWith(context, args);
+ } else if (resolveOrReject === false) {
+ dfd.rejectWith(context, args);
+ }
+ promise.abort = dfd.promise;
+ return this._enhancePromise(promise);
+ },
- // Creates and returns a Promise object enhanced with
- // the jqXHR methods abort, success, error and complete:
- _getXHRPromise: function (resolveOrReject, context, args) {
- var dfd = $.Deferred(),
- promise = dfd.promise();
- context = context || this.options.context || promise;
- if (resolveOrReject === true) {
- dfd.resolveWith(context, args);
- } else if (resolveOrReject === false) {
- dfd.rejectWith(context, args);
- }
- promise.abort = dfd.promise;
- return this._enhancePromise(promise);
- },
+ // Adds convenience methods to the data callback argument:
+ _addConvenienceMethods: function (e, data) {
+ var that = this,
+ getPromise = function (args) {
+ return $.Deferred().resolveWith(that, args).promise();
+ };
+ data.process = function (resolveFunc, rejectFunc) {
+ if (resolveFunc || rejectFunc) {
+ data._processQueue = this._processQueue = (this._processQueue ||
+ getPromise([this]))
+ [that._promisePipe](function () {
+ if (data.errorThrown) {
+ return $.Deferred().rejectWith(that, [data]).promise();
+ }
+ return getPromise(arguments);
+ })
+ [that._promisePipe](resolveFunc, rejectFunc);
+ }
+ return this._processQueue || getPromise([this]);
+ };
+ data.submit = function () {
+ if (this.state() !== 'pending') {
+ data.jqXHR = this.jqXHR =
+ that._trigger(
+ 'submit',
+ $.Event('submit', { delegatedEvent: e }),
+ this
+ ) !== false && that._onSend(e, this);
+ }
+ return this.jqXHR || that._getXHRPromise();
+ };
+ data.abort = function () {
+ if (this.jqXHR) {
+ return this.jqXHR.abort();
+ }
+ this.errorThrown = 'abort';
+ that._trigger('fail', null, this);
+ return that._getXHRPromise(false);
+ };
+ data.state = function () {
+ if (this.jqXHR) {
+ return that._getDeferredState(this.jqXHR);
+ }
+ if (this._processQueue) {
+ return that._getDeferredState(this._processQueue);
+ }
+ };
+ data.processing = function () {
+ return (
+ !this.jqXHR &&
+ this._processQueue &&
+ that._getDeferredState(this._processQueue) === 'pending'
+ );
+ };
+ data.progress = function () {
+ return this._progress;
+ };
+ data.response = function () {
+ return this._response;
+ };
+ },
- // Uploads a file in multiple, sequential requests
- // by splitting the file up in multiple blob chunks.
- // If the second parameter is true, only tests if the file
- // should be uploaded in chunks, but does not invoke any
- // upload requests:
- _chunkedUpload: function (options, testOnly) {
- var that = this,
- file = options.files[0],
- fs = file.size,
- ub = options.uploadedBytes = options.uploadedBytes || 0,
- mcs = options.maxChunkSize || fs,
- // Use the Blob methods with the slice implementation
- // according to the W3C Blob API specification:
- slice = file.webkitSlice || file.mozSlice || file.slice,
- upload,
- n,
- jqXHR,
- pipe;
- if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
- options.data) {
- return false;
- }
- if (testOnly) {
- return true;
+ // Parses the Range header from the server response
+ // and returns the uploaded bytes:
+ _getUploadedBytes: function (jqXHR) {
+ var range = jqXHR.getResponseHeader('Range'),
+ parts = range && range.split('-'),
+ upperBytesPos = parts && parts.length > 1 && parseInt(parts[1], 10);
+ return upperBytesPos && upperBytesPos + 1;
+ },
+
+ // Uploads a file in multiple, sequential requests
+ // by splitting the file up in multiple blob chunks.
+ // If the second parameter is true, only tests if the file
+ // should be uploaded in chunks, but does not invoke any
+ // upload requests:
+ _chunkedUpload: function (options, testOnly) {
+ options.uploadedBytes = options.uploadedBytes || 0;
+ var that = this,
+ file = options.files[0],
+ fs = file.size,
+ ub = options.uploadedBytes,
+ mcs = options.maxChunkSize || fs,
+ slice = this._blobSlice,
+ dfd = $.Deferred(),
+ promise = dfd.promise(),
+ jqXHR,
+ upload;
+ if (
+ !(
+ this._isXHRUpload(options) &&
+ slice &&
+ (ub || ($.type(mcs) === 'function' ? mcs(options) : mcs) < fs)
+ ) ||
+ options.data
+ ) {
+ return false;
+ }
+ if (testOnly) {
+ return true;
+ }
+ if (ub >= fs) {
+ file.error = options.i18n('uploadedBytes');
+ return this._getXHRPromise(false, options.context, [
+ null,
+ 'error',
+ file.error
+ ]);
+ }
+ // The chunk upload method:
+ upload = function () {
+ // Clone the options object for each chunk upload:
+ var o = $.extend({}, options),
+ currentLoaded = o._progress.loaded;
+ o.blob = slice.call(
+ file,
+ ub,
+ ub + ($.type(mcs) === 'function' ? mcs(o) : mcs),
+ file.type
+ );
+ // Store the current chunk size, as the blob itself
+ // will be dereferenced after data processing:
+ o.chunkSize = o.blob.size;
+ // Expose the chunk bytes position range:
+ o.contentRange =
+ 'bytes ' + ub + '-' + (ub + o.chunkSize - 1) + '/' + fs;
+ // Trigger chunkbeforesend to allow form data to be updated for this chunk
+ that._trigger('chunkbeforesend', null, o);
+ // Process the upload data (the blob and potential form data):
+ that._initXHRData(o);
+ // Add progress listeners for this chunk upload:
+ that._initProgressListener(o);
+ jqXHR = (
+ (that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
+ that._getXHRPromise(false, o.context)
+ )
+ .done(function (result, textStatus, jqXHR) {
+ ub = that._getUploadedBytes(jqXHR) || ub + o.chunkSize;
+ // Create a progress event if no final progress event
+ // with loaded equaling total has been triggered
+ // for this chunk:
+ if (currentLoaded + o.chunkSize - o._progress.loaded) {
+ that._onProgress(
+ $.Event('progress', {
+ lengthComputable: true,
+ loaded: ub - o.uploadedBytes,
+ total: ub - o.uploadedBytes
+ }),
+ o
+ );
}
- if (ub >= fs) {
- file.error = 'uploadedBytes';
- return this._getXHRPromise(
- false,
- options.context,
- [null, 'error', file.error]
- );
+ options.uploadedBytes = o.uploadedBytes = ub;
+ o.result = result;
+ o.textStatus = textStatus;
+ o.jqXHR = jqXHR;
+ that._trigger('chunkdone', null, o);
+ that._trigger('chunkalways', null, o);
+ if (ub < fs) {
+ // File upload not yet complete,
+ // continue with the next chunk:
+ upload();
+ } else {
+ dfd.resolveWith(o.context, [result, textStatus, jqXHR]);
}
- // n is the number of blobs to upload,
- // calculated via filesize, uploaded bytes and max chunk size:
- n = Math.ceil((fs - ub) / mcs);
- // The chunk upload method accepting the chunk number as parameter:
- upload = function (i) {
- if (!i) {
- return that._getXHRPromise(true, options.context);
- }
- // Upload the blobs in sequential order:
- return upload(i -= 1).pipe(function () {
- // Clone the options object for each chunk upload:
- var o = $.extend({}, options);
- o.blob = slice.call(
- file,
- ub + i * mcs,
- ub + (i + 1) * mcs
- );
- // Expose the chunk index:
- o.chunkIndex = i;
- // Expose the number of chunks:
- o.chunksNumber = n;
- // Store the current chunk size, as the blob itself
- // will be dereferenced after data processing:
- o.chunkSize = o.blob.size;
- // Process the upload data (the blob and potential form data):
- that._initXHRData(o);
- // Add progress listeners for this chunk upload:
- that._initProgressListener(o);
- jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context))
- .done(function () {
- // Create a progress event if upload is done and
- // no progress event has been invoked for this chunk:
- if (!o.loaded) {
- that._onProgress($.Event('progress', {
- lengthComputable: true,
- loaded: o.chunkSize,
- total: o.chunkSize
- }), o);
- }
- options.uploadedBytes = o.uploadedBytes +=
- o.chunkSize;
- });
- return jqXHR;
- });
- };
- // Return the piped Promise object, enhanced with an abort method,
- // which is delegated to the jqXHR object of the current upload,
- // and jqXHR callbacks mapped to the equivalent Promise methods:
- pipe = upload(n);
- pipe.abort = function () {
- return jqXHR.abort();
- };
- return this._enhancePromise(pipe);
- },
+ })
+ .fail(function (jqXHR, textStatus, errorThrown) {
+ o.jqXHR = jqXHR;
+ o.textStatus = textStatus;
+ o.errorThrown = errorThrown;
+ that._trigger('chunkfail', null, o);
+ that._trigger('chunkalways', null, o);
+ dfd.rejectWith(o.context, [jqXHR, textStatus, errorThrown]);
+ })
+ .always(function () {
+ that._deinitProgressListener(o);
+ });
+ };
+ this._enhancePromise(promise);
+ promise.abort = function () {
+ return jqXHR.abort();
+ };
+ upload();
+ return promise;
+ },
- _beforeSend: function (e, data) {
- if (this._active === 0) {
- // the start callback is triggered when an upload starts
- // and no other uploads are currently running,
- // equivalent to the global ajaxStart event:
- this._trigger('start');
- // Set timer for global bitrate progress calculation:
- this._bitrateTimer = new this._BitrateTimer();
- }
- this._active += 1;
- // Initialize the global progress values:
- this._loaded += data.uploadedBytes || 0;
- this._total += this._getTotal(data.files);
- },
+ _beforeSend: function (e, data) {
+ if (this._active === 0) {
+ // the start callback is triggered when an upload starts
+ // and no other uploads are currently running,
+ // equivalent to the global ajaxStart event:
+ this._trigger('start');
+ // Set timer for global bitrate progress calculation:
+ this._bitrateTimer = new this._BitrateTimer();
+ // Reset the global progress values:
+ this._progress.loaded = this._progress.total = 0;
+ this._progress.bitrate = 0;
+ }
+ // Make sure the container objects for the .response() and
+ // .progress() methods on the data object are available
+ // and reset to their initial state:
+ this._initResponseObject(data);
+ this._initProgressObject(data);
+ data._progress.loaded = data.loaded = data.uploadedBytes || 0;
+ data._progress.total = data.total = this._getTotal(data.files) || 1;
+ data._progress.bitrate = data.bitrate = 0;
+ this._active += 1;
+ // Initialize the global progress values:
+ this._progress.loaded += data.loaded;
+ this._progress.total += data.total;
+ },
- _onDone: function (result, textStatus, jqXHR, options) {
- if (!this._isXHRUpload(options)) {
- // Create a progress event for each iframe load:
- this._onProgress($.Event('progress', {
- lengthComputable: true,
- loaded: 1,
- total: 1
- }), options);
- }
- options.result = result;
- options.textStatus = textStatus;
- options.jqXHR = jqXHR;
- this._trigger('done', null, options);
- },
+ _onDone: function (result, textStatus, jqXHR, options) {
+ var total = options._progress.total,
+ response = options._response;
+ if (options._progress.loaded < total) {
+ // Create a progress event if no final progress event
+ // with loaded equaling total has been triggered:
+ this._onProgress(
+ $.Event('progress', {
+ lengthComputable: true,
+ loaded: total,
+ total: total
+ }),
+ options
+ );
+ }
+ response.result = options.result = result;
+ response.textStatus = options.textStatus = textStatus;
+ response.jqXHR = options.jqXHR = jqXHR;
+ this._trigger('done', null, options);
+ },
- _onFail: function (jqXHR, textStatus, errorThrown, options) {
- options.jqXHR = jqXHR;
- options.textStatus = textStatus;
- options.errorThrown = errorThrown;
- this._trigger('fail', null, options);
- if (options.recalculateProgress) {
- // Remove the failed (error or abort) file upload from
- // the global progress calculation:
- this._loaded -= options.loaded || options.uploadedBytes || 0;
- this._total -= options.total || this._getTotal(options.files);
- }
- },
+ _onFail: function (jqXHR, textStatus, errorThrown, options) {
+ var response = options._response;
+ if (options.recalculateProgress) {
+ // Remove the failed (error or abort) file upload from
+ // the global progress calculation:
+ this._progress.loaded -= options._progress.loaded;
+ this._progress.total -= options._progress.total;
+ }
+ response.jqXHR = options.jqXHR = jqXHR;
+ response.textStatus = options.textStatus = textStatus;
+ response.errorThrown = options.errorThrown = errorThrown;
+ this._trigger('fail', null, options);
+ },
- _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
- this._active -= 1;
- options.textStatus = textStatus;
- if (jqXHRorError && jqXHRorError.always) {
- options.jqXHR = jqXHRorError;
- options.result = jqXHRorResult;
- } else {
- options.jqXHR = jqXHRorResult;
- options.errorThrown = jqXHRorError;
- }
- this._trigger('always', null, options);
- if (this._active === 0) {
- // The stop callback is triggered when all uploads have
- // been completed, equivalent to the global ajaxStop event:
- this._trigger('stop');
- // Reset the global progress values:
- this._loaded = this._total = 0;
- this._bitrateTimer = null;
- }
- },
+ _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
+ // jqXHRorResult, textStatus and jqXHRorError are added to the
+ // options object via done and fail callbacks
+ this._trigger('always', null, options);
+ },
- _onSend: function (e, data) {
- var that = this,
- jqXHR,
- slot,
- pipe,
- options = that._getAJAXSettings(data),
- send = function (resolve, args) {
- that._sending += 1;
- // Set timer for bitrate progress calculation:
- options._bitrateTimer = new that._BitrateTimer();
- jqXHR = jqXHR || (
- (resolve !== false &&
- that._trigger('send', e, options) !== false &&
- (that._chunkedUpload(options) || $.ajax(options))) ||
- that._getXHRPromise(false, options.context, args)
- ).done(function (result, textStatus, jqXHR) {
- that._onDone(result, textStatus, jqXHR, options);
- }).fail(function (jqXHR, textStatus, errorThrown) {
- that._onFail(jqXHR, textStatus, errorThrown, options);
- }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
- that._sending -= 1;
- that._onAlways(
- jqXHRorResult,
- textStatus,
- jqXHRorError,
- options
- );
- if (options.limitConcurrentUploads &&
- options.limitConcurrentUploads > that._sending) {
- // Start the next queued upload,
- // that has not been aborted:
- var nextSlot = that._slots.shift(),
- isPending;
- while (nextSlot) {
- // jQuery 1.6 doesn't provide .state(),
- // while jQuery 1.8+ removed .isRejected():
- isPending = nextSlot.state ?
- nextSlot.state() === 'pending' :
- !nextSlot.isRejected();
- if (isPending) {
- nextSlot.resolve();
- break;
- }
- nextSlot = that._slots.shift();
- }
- }
- });
- return jqXHR;
- };
- this._beforeSend(e, options);
- if (this.options.sequentialUploads ||
- (this.options.limitConcurrentUploads &&
- this.options.limitConcurrentUploads <= this._sending)) {
- if (this.options.limitConcurrentUploads > 1) {
- slot = $.Deferred();
- this._slots.push(slot);
- pipe = slot.pipe(send);
- } else {
- pipe = (this._sequence = this._sequence.pipe(send, send));
- }
- // Return the piped Promise object, enhanced with an abort method,
- // which is delegated to the jqXHR object of the current upload,
- // and jqXHR callbacks mapped to the equivalent Promise methods:
- pipe.abort = function () {
- var args = [undefined, 'abort', 'abort'];
- if (!jqXHR) {
- if (slot) {
- slot.rejectWith(pipe, args);
- }
- return send(false, args);
+ _onSend: function (e, data) {
+ if (!data.submit) {
+ this._addConvenienceMethods(e, data);
+ }
+ var that = this,
+ jqXHR,
+ aborted,
+ slot,
+ pipe,
+ options = that._getAJAXSettings(data),
+ send = function () {
+ that._sending += 1;
+ // Set timer for bitrate progress calculation:
+ options._bitrateTimer = new that._BitrateTimer();
+ jqXHR =
+ jqXHR ||
+ (
+ ((aborted ||
+ that._trigger(
+ 'send',
+ $.Event('send', { delegatedEvent: e }),
+ options
+ ) === false) &&
+ that._getXHRPromise(false, options.context, aborted)) ||
+ that._chunkedUpload(options) ||
+ $.ajax(options)
+ )
+ .done(function (result, textStatus, jqXHR) {
+ that._onDone(result, textStatus, jqXHR, options);
+ })
+ .fail(function (jqXHR, textStatus, errorThrown) {
+ that._onFail(jqXHR, textStatus, errorThrown, options);
+ })
+ .always(function (jqXHRorResult, textStatus, jqXHRorError) {
+ that._deinitProgressListener(options);
+ that._onAlways(
+ jqXHRorResult,
+ textStatus,
+ jqXHRorError,
+ options
+ );
+ that._sending -= 1;
+ that._active -= 1;
+ if (
+ options.limitConcurrentUploads &&
+ options.limitConcurrentUploads > that._sending
+ ) {
+ // Start the next queued upload,
+ // that has not been aborted:
+ var nextSlot = that._slots.shift();
+ while (nextSlot) {
+ if (that._getDeferredState(nextSlot) === 'pending') {
+ nextSlot.resolve();
+ break;
}
- return jqXHR.abort();
- };
- return this._enhancePromise(pipe);
+ nextSlot = that._slots.shift();
+ }
+ }
+ if (that._active === 0) {
+ // The stop callback is triggered when all uploads have
+ // been completed, equivalent to the global ajaxStop event:
+ that._trigger('stop');
+ }
+ });
+ return jqXHR;
+ };
+ this._beforeSend(e, options);
+ if (
+ this.options.sequentialUploads ||
+ (this.options.limitConcurrentUploads &&
+ this.options.limitConcurrentUploads <= this._sending)
+ ) {
+ if (this.options.limitConcurrentUploads > 1) {
+ slot = $.Deferred();
+ this._slots.push(slot);
+ pipe = slot[that._promisePipe](send);
+ } else {
+ this._sequence = this._sequence[that._promisePipe](send, send);
+ pipe = this._sequence;
+ }
+ // Return the piped Promise object, enhanced with an abort method,
+ // which is delegated to the jqXHR object of the current upload,
+ // and jqXHR callbacks mapped to the equivalent Promise methods:
+ pipe.abort = function () {
+ aborted = [undefined, 'abort', 'abort'];
+ if (!jqXHR) {
+ if (slot) {
+ slot.rejectWith(options.context, aborted);
}
return send();
- },
+ }
+ return jqXHR.abort();
+ };
+ return this._enhancePromise(pipe);
+ }
+ return send();
+ },
- _onAdd: function (e, data) {
- var that = this,
- result = true,
- options = $.extend({}, this.options, data),
- limit = options.limitMultiFileUploads,
- paramName = this._getParamName(options),
- paramNameSet,
- paramNameSlice,
- fileSet,
- i;
- if (!(options.singleFileUploads || limit) ||
- !this._isXHRUpload(options)) {
- fileSet = [data.files];
- paramNameSet = [paramName];
- } else if (!options.singleFileUploads && limit) {
- fileSet = [];
- paramNameSet = [];
- for (i = 0; i < data.files.length; i += limit) {
- fileSet.push(data.files.slice(i, i + limit));
- paramNameSlice = paramName.slice(i, i + limit);
- if (!paramNameSlice.length) {
- paramNameSlice = paramName;
- }
- paramNameSet.push(paramNameSlice);
- }
- } else {
- paramNameSet = paramName;
+ _onAdd: function (e, data) {
+ var that = this,
+ result = true,
+ options = $.extend({}, this.options, data),
+ files = data.files,
+ filesLength = files.length,
+ limit = options.limitMultiFileUploads,
+ limitSize = options.limitMultiFileUploadSize,
+ overhead = options.limitMultiFileUploadSizeOverhead,
+ batchSize = 0,
+ paramName = this._getParamName(options),
+ paramNameSet,
+ paramNameSlice,
+ fileSet,
+ i,
+ j = 0;
+ if (!filesLength) {
+ return false;
+ }
+ if (limitSize && files[0].size === undefined) {
+ limitSize = undefined;
+ }
+ if (
+ !(options.singleFileUploads || limit || limitSize) ||
+ !this._isXHRUpload(options)
+ ) {
+ fileSet = [files];
+ paramNameSet = [paramName];
+ } else if (!(options.singleFileUploads || limitSize) && limit) {
+ fileSet = [];
+ paramNameSet = [];
+ for (i = 0; i < filesLength; i += limit) {
+ fileSet.push(files.slice(i, i + limit));
+ paramNameSlice = paramName.slice(i, i + limit);
+ if (!paramNameSlice.length) {
+ paramNameSlice = paramName;
+ }
+ paramNameSet.push(paramNameSlice);
+ }
+ } else if (!options.singleFileUploads && limitSize) {
+ fileSet = [];
+ paramNameSet = [];
+ for (i = 0; i < filesLength; i = i + 1) {
+ batchSize += files[i].size + overhead;
+ if (
+ i + 1 === filesLength ||
+ batchSize + files[i + 1].size + overhead > limitSize ||
+ (limit && i + 1 - j >= limit)
+ ) {
+ fileSet.push(files.slice(j, i + 1));
+ paramNameSlice = paramName.slice(j, i + 1);
+ if (!paramNameSlice.length) {
+ paramNameSlice = paramName;
}
- data.originalFiles = data.files;
- $.each(fileSet || data.files, function (index, element) {
- var newData = $.extend({}, data);
- newData.files = fileSet ? element : [element];
- newData.paramName = paramNameSet[index];
- newData.submit = function () {
- newData.jqXHR = this.jqXHR =
- (that._trigger('submit', e, this) !== false) &&
- that._onSend(e, this);
- return this.jqXHR;
- };
- return (result = that._trigger('add', e, newData));
- });
- return result;
- },
+ paramNameSet.push(paramNameSlice);
+ j = i + 1;
+ batchSize = 0;
+ }
+ }
+ } else {
+ paramNameSet = paramName;
+ }
+ data.originalFiles = files;
+ $.each(fileSet || files, function (index, element) {
+ var newData = $.extend({}, data);
+ newData.files = fileSet ? element : [element];
+ newData.paramName = paramNameSet[index];
+ that._initResponseObject(newData);
+ that._initProgressObject(newData);
+ that._addConvenienceMethods(e, newData);
+ result = that._trigger(
+ 'add',
+ $.Event('add', { delegatedEvent: e }),
+ newData
+ );
+ return result;
+ });
+ return result;
+ },
- _replaceFileInput: function (input) {
- var inputClone = input.clone(true);
- $('').append(inputClone)[0].reset();
- // Detaching allows to insert the fileInput on another form
- // without loosing the file input value:
- input.after(inputClone).detach();
- // Avoid memory leaks with the detached file input:
- $.cleanData(input.unbind('remove'));
- // Replace the original file input element in the fileInput
- // collection with the clone, which has been copied including
- // event handlers:
- this.options.fileInput = this.options.fileInput.map(function (i, el) {
- if (el === input[0]) {
- return inputClone[0];
- }
- return el;
- });
- // If the widget has been initialized on the file input itself,
- // override this.element with the file input clone:
- if (input[0] === this.element[0]) {
- this.element = inputClone;
- }
- },
+ _replaceFileInput: function (data) {
+ var input = data.fileInput,
+ inputClone = input.clone(true),
+ restoreFocus = input.is(document.activeElement);
+ // Add a reference for the new cloned file input to the data argument:
+ data.fileInputClone = inputClone;
+ $('').append(inputClone)[0].reset();
+ // Detaching allows to insert the fileInput on another form
+ // without loosing the file input value:
+ input.after(inputClone).detach();
+ // If the fileInput had focus before it was detached,
+ // restore focus to the inputClone.
+ if (restoreFocus) {
+ inputClone.trigger('focus');
+ }
+ // Avoid memory leaks with the detached file input:
+ $.cleanData(input.off('remove'));
+ // Replace the original file input element in the fileInput
+ // elements set with the clone, which has been copied including
+ // event handlers:
+ this.options.fileInput = this.options.fileInput.map(function (i, el) {
+ if (el === input[0]) {
+ return inputClone[0];
+ }
+ return el;
+ });
+ // If the widget has been initialized on the file input itself,
+ // override this.element with the file input clone:
+ if (input[0] === this.element[0]) {
+ this.element = inputClone;
+ }
+ },
- _handleFileTreeEntry: function (entry, path) {
- var that = this,
- dfd = $.Deferred(),
- errorHandler = function () {
- dfd.reject();
- },
- dirReader;
- path = path || '';
- if (entry.isFile) {
- entry.file(function (file) {
- file.relativePath = path;
- dfd.resolve(file);
- }, errorHandler);
- } else if (entry.isDirectory) {
- dirReader = entry.createReader();
- dirReader.readEntries(function (entries) {
- that._handleFileTreeEntries(
- entries,
- path + entry.name + '/'
- ).done(function (files) {
- dfd.resolve(files);
- }).fail(errorHandler);
- }, errorHandler);
+ _handleFileTreeEntry: function (entry, path) {
+ var that = this,
+ dfd = $.Deferred(),
+ entries = [],
+ dirReader,
+ errorHandler = function (e) {
+ if (e && !e.entry) {
+ e.entry = entry;
+ }
+ // Since $.when returns immediately if one
+ // Deferred is rejected, we use resolve instead.
+ // This allows valid files and invalid items
+ // to be returned together in one set:
+ dfd.resolve([e]);
+ },
+ successHandler = function (entries) {
+ that
+ ._handleFileTreeEntries(entries, path + entry.name + '/')
+ .done(function (files) {
+ dfd.resolve(files);
+ })
+ .fail(errorHandler);
+ },
+ readEntries = function () {
+ dirReader.readEntries(function (results) {
+ if (!results.length) {
+ successHandler(entries);
} else {
- errorHandler();
+ entries = entries.concat(results);
+ readEntries();
}
- return dfd.promise();
- },
+ }, errorHandler);
+ };
+ // eslint-disable-next-line no-param-reassign
+ path = path || '';
+ if (entry.isFile) {
+ if (entry._file) {
+ // Workaround for Chrome bug #149735
+ entry._file.relativePath = path;
+ dfd.resolve(entry._file);
+ } else {
+ entry.file(function (file) {
+ file.relativePath = path;
+ dfd.resolve(file);
+ }, errorHandler);
+ }
+ } else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ readEntries();
+ } else {
+ // Return an empty list for file system items
+ // other than files or directories:
+ dfd.resolve([]);
+ }
+ return dfd.promise();
+ },
- _handleFileTreeEntries: function (entries, path) {
- var that = this;
- return $.when.apply(
- $,
- $.map(entries, function (entry) {
- return that._handleFileTreeEntry(entry, path);
- })
- ).pipe(function () {
- return Array.prototype.concat.apply(
- [],
- arguments
- );
- });
- },
+ _handleFileTreeEntries: function (entries, path) {
+ var that = this;
+ return $.when
+ .apply(
+ $,
+ $.map(entries, function (entry) {
+ return that._handleFileTreeEntry(entry, path);
+ })
+ )
+ [this._promisePipe](function () {
+ return Array.prototype.concat.apply([], arguments);
+ });
+ },
- _getDroppedFiles: function (dataTransfer) {
- dataTransfer = dataTransfer || {};
- var items = dataTransfer.items;
- if (items && items.length && (items[0].webkitGetAsEntry ||
- items[0].getAsEntry)) {
- return this._handleFileTreeEntries(
- $.map(items, function (item) {
- if (item.webkitGetAsEntry) {
- return item.webkitGetAsEntry();
- }
- return item.getAsEntry();
- })
- );
+ _getDroppedFiles: function (dataTransfer) {
+ // eslint-disable-next-line no-param-reassign
+ dataTransfer = dataTransfer || {};
+ var items = dataTransfer.items;
+ if (
+ items &&
+ items.length &&
+ (items[0].webkitGetAsEntry || items[0].getAsEntry)
+ ) {
+ return this._handleFileTreeEntries(
+ $.map(items, function (item) {
+ var entry;
+ if (item.webkitGetAsEntry) {
+ entry = item.webkitGetAsEntry();
+ if (entry) {
+ // Workaround for Chrome bug #149735:
+ entry._file = item.getAsFile();
+ }
+ return entry;
}
- return $.Deferred().resolve(
- $.makeArray(dataTransfer.files)
- ).promise();
- },
+ return item.getAsEntry();
+ })
+ );
+ }
+ return $.Deferred().resolve($.makeArray(dataTransfer.files)).promise();
+ },
- _getFileInputFiles: function (fileInput) {
- fileInput = $(fileInput);
- var entries = fileInput.prop('webkitEntries') ||
- fileInput.prop('entries'),
- files,
- value;
- if (entries && entries.length) {
- return this._handleFileTreeEntries(entries);
- }
- files = $.makeArray(fileInput.prop('files'));
- if (!files.length) {
- value = fileInput.prop('value');
- if (!value) {
- return $.Deferred().reject([]).promise();
- }
- // If the files property is not available, the browser does not
- // support the File API and we add a pseudo File object with
- // the input value as name with path information removed:
- files = [{name: value.replace(/^.*\\/, '')}];
- }
- return $.Deferred().resolve(files).promise();
- },
+ _getSingleFileInputFiles: function (fileInput) {
+ // eslint-disable-next-line no-param-reassign
+ fileInput = $(fileInput);
+ var entries =
+ fileInput.prop('webkitEntries') || fileInput.prop('entries'),
+ files,
+ value;
+ if (entries && entries.length) {
+ return this._handleFileTreeEntries(entries);
+ }
+ files = $.makeArray(fileInput.prop('files'));
+ if (!files.length) {
+ value = fileInput.prop('value');
+ if (!value) {
+ return $.Deferred().resolve([]).promise();
+ }
+ // If the files property is not available, the browser does not
+ // support the File API and we add a pseudo File object with
+ // the input value as name with path information removed:
+ files = [{ name: value.replace(/^.*\\/, '') }];
+ } else if (files[0].name === undefined && files[0].fileName) {
+ // File normalization for Safari 4 and Firefox 3:
+ $.each(files, function (index, file) {
+ file.name = file.fileName;
+ file.size = file.fileSize;
+ });
+ }
+ return $.Deferred().resolve(files).promise();
+ },
- _onChange: function (e) {
- var that = e.data.fileupload,
- data = {
- fileInput: $(e.target),
- form: $(e.target.form)
- };
- that._getFileInputFiles(data.fileInput).always(function (files) {
- data.files = files;
- if (that.options.replaceFileInput) {
- that._replaceFileInput(data.fileInput);
- }
- if (that._trigger('change', e, data) !== false) {
- that._onAdd(e, data);
- }
- });
- },
+ _getFileInputFiles: function (fileInput) {
+ if (!(fileInput instanceof $) || fileInput.length === 1) {
+ return this._getSingleFileInputFiles(fileInput);
+ }
+ return $.when
+ .apply($, $.map(fileInput, this._getSingleFileInputFiles))
+ [this._promisePipe](function () {
+ return Array.prototype.concat.apply([], arguments);
+ });
+ },
- _onPaste: function (e) {
- var that = e.data.fileupload,
- cbd = e.originalEvent.clipboardData,
- items = (cbd && cbd.items) || [],
- data = {files: []};
- $.each(items, function (index, item) {
- var file = item.getAsFile && item.getAsFile();
- if (file) {
- data.files.push(file);
- }
- });
- if (that._trigger('paste', e, data) === false ||
- that._onAdd(e, data) === false) {
- return false;
- }
- },
+ _onChange: function (e) {
+ var that = this,
+ data = {
+ fileInput: $(e.target),
+ form: $(e.target.form)
+ };
+ this._getFileInputFiles(data.fileInput).always(function (files) {
+ data.files = files;
+ if (that.options.replaceFileInput) {
+ that._replaceFileInput(data);
+ }
+ if (
+ that._trigger(
+ 'change',
+ $.Event('change', { delegatedEvent: e }),
+ data
+ ) !== false
+ ) {
+ that._onAdd(e, data);
+ }
+ });
+ },
- _onDrop: function (e) {
- e.preventDefault();
- var that = e.data.fileupload,
- dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer,
- data = {};
- that._getDroppedFiles(dataTransfer).always(function (files) {
- data.files = files;
- if (that._trigger('drop', e, data) !== false) {
- that._onAdd(e, data);
- }
- });
- },
+ _onPaste: function (e) {
+ var items =
+ e.originalEvent &&
+ e.originalEvent.clipboardData &&
+ e.originalEvent.clipboardData.items,
+ data = { files: [] };
+ if (items && items.length) {
+ $.each(items, function (index, item) {
+ var file = item.getAsFile && item.getAsFile();
+ if (file) {
+ data.files.push(file);
+ }
+ });
+ if (
+ this._trigger(
+ 'paste',
+ $.Event('paste', { delegatedEvent: e }),
+ data
+ ) !== false
+ ) {
+ this._onAdd(e, data);
+ }
+ }
+ },
- _onDragOver: function (e) {
- var that = e.data.fileupload,
- dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer;
- if (that._trigger('dragover', e) === false) {
- return false;
- }
- if (dataTransfer) {
- dataTransfer.dropEffect = 'copy';
- }
- e.preventDefault();
- },
+ _onDrop: function (e) {
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
+ var that = this,
+ dataTransfer = e.dataTransfer,
+ data = {};
+ if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
+ e.preventDefault();
+ this._getDroppedFiles(dataTransfer).always(function (files) {
+ data.files = files;
+ if (
+ that._trigger(
+ 'drop',
+ $.Event('drop', { delegatedEvent: e }),
+ data
+ ) !== false
+ ) {
+ that._onAdd(e, data);
+ }
+ });
+ }
+ },
- _initEventHandlers: function () {
- var ns = this.options.namespace;
- if (this._isXHRUpload(this.options)) {
- this.options.dropZone
- .bind('dragover.' + ns, {fileupload: this}, this._onDragOver)
- .bind('drop.' + ns, {fileupload: this}, this._onDrop)
- .bind('paste.' + ns, {fileupload: this}, this._onPaste);
- }
- this.options.fileInput
- .bind('change.' + ns, {fileupload: this}, this._onChange);
- },
+ _onDragOver: getDragHandler('dragover'),
- _destroyEventHandlers: function () {
- var ns = this.options.namespace;
- this.options.dropZone
- .unbind('dragover.' + ns, this._onDragOver)
- .unbind('drop.' + ns, this._onDrop)
- .unbind('paste.' + ns, this._onPaste);
- this.options.fileInput
- .unbind('change.' + ns, this._onChange);
- },
+ _onDragEnter: getDragHandler('dragenter'),
- _setOption: function (key, value) {
- var refresh = $.inArray(key, this._refreshOptionsList) !== -1;
- if (refresh) {
- this._destroyEventHandlers();
- }
- $.Widget.prototype._setOption.call(this, key, value);
- if (refresh) {
- this._initSpecialOptions();
- this._initEventHandlers();
- }
- },
+ _onDragLeave: getDragHandler('dragleave'),
- _initSpecialOptions: function () {
- var options = this.options;
- if (options.fileInput === undefined) {
- options.fileInput = this.element.is('input[type="file"]') ?
- this.element : this.element.find('input[type="file"]');
- } else if (!(options.fileInput instanceof $)) {
- options.fileInput = $(options.fileInput);
- }
- if (!(options.dropZone instanceof $)) {
- options.dropZone = $(options.dropZone);
- }
- },
+ _initEventHandlers: function () {
+ if (this._isXHRUpload(this.options)) {
+ this._on(this.options.dropZone, {
+ dragover: this._onDragOver,
+ drop: this._onDrop,
+ // event.preventDefault() on dragenter is required for IE10+:
+ dragenter: this._onDragEnter,
+ // dragleave is not required, but added for completeness:
+ dragleave: this._onDragLeave
+ });
+ this._on(this.options.pasteZone, {
+ paste: this._onPaste
+ });
+ }
+ if ($.support.fileInput) {
+ this._on(this.options.fileInput, {
+ change: this._onChange
+ });
+ }
+ },
- _create: function () {
- var options = this.options;
- // Initialize options set via HTML5 data-attributes:
- $.extend(options, $(this.element[0].cloneNode(false)).data());
- options.namespace = options.namespace || this.widgetName;
- this._initSpecialOptions();
- this._slots = [];
- this._sequence = this._getXHRPromise(true);
- this._sending = this._active = this._loaded = this._total = 0;
- this._initEventHandlers();
- },
+ _destroyEventHandlers: function () {
+ this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
+ this._off(this.options.pasteZone, 'paste');
+ this._off(this.options.fileInput, 'change');
+ },
- destroy: function () {
- this._destroyEventHandlers();
- $.Widget.prototype.destroy.call(this);
- },
+ _destroy: function () {
+ this._destroyEventHandlers();
+ },
- enable: function () {
- var wasDisabled = false;
- if (this.options.disabled) {
- wasDisabled = true;
- }
- $.Widget.prototype.enable.call(this);
- if (wasDisabled) {
- this._initEventHandlers();
- }
- },
+ _setOption: function (key, value) {
+ var reinit = $.inArray(key, this._specialOptions) !== -1;
+ if (reinit) {
+ this._destroyEventHandlers();
+ }
+ this._super(key, value);
+ if (reinit) {
+ this._initSpecialOptions();
+ this._initEventHandlers();
+ }
+ },
- disable: function () {
- if (!this.options.disabled) {
- this._destroyEventHandlers();
- }
- $.Widget.prototype.disable.call(this);
- },
+ _initSpecialOptions: function () {
+ var options = this.options;
+ if (options.fileInput === undefined) {
+ options.fileInput = this.element.is('input[type="file"]')
+ ? this.element
+ : this.element.find('input[type="file"]');
+ } else if (!(options.fileInput instanceof $)) {
+ options.fileInput = $(options.fileInput);
+ }
+ if (!(options.dropZone instanceof $)) {
+ options.dropZone = $(options.dropZone);
+ }
+ if (!(options.pasteZone instanceof $)) {
+ options.pasteZone = $(options.pasteZone);
+ }
+ },
- // This method is exposed to the widget API and allows adding files
- // using the fileupload API. The data parameter accepts an object which
- // must have a files property and can contain additional options:
- // .fileupload('add', {files: filesList});
- add: function (data) {
- var that = this;
- if (!data || this.options.disabled) {
- return;
- }
- if (data.fileInput && !data.files) {
- this._getFileInputFiles(data.fileInput).always(function (files) {
- data.files = files;
- that._onAdd(null, data);
- });
- } else {
- data.files = $.makeArray(data.files);
- this._onAdd(null, data);
- }
- },
+ _getRegExp: function (str) {
+ var parts = str.split('/'),
+ modifiers = parts.pop();
+ parts.shift();
+ return new RegExp(parts.join('/'), modifiers);
+ },
- // This method is exposed to the widget API and allows sending files
- // using the fileupload API. The data parameter accepts an object which
- // must have a files or fileInput property and can contain additional options:
- // .fileupload('send', {files: filesList});
- // The method returns a Promise object for the file upload call.
- send: function (data) {
- if (data && !this.options.disabled) {
- if (data.fileInput && !data.files) {
- var that = this,
- dfd = $.Deferred(),
- promise = dfd.promise(),
- jqXHR,
- aborted;
- promise.abort = function () {
- aborted = true;
- if (jqXHR) {
- return jqXHR.abort();
- }
- dfd.reject(null, 'abort', 'abort');
- return promise;
- };
- this._getFileInputFiles(data.fileInput).always(
- function (files) {
- if (aborted) {
- return;
- }
- data.files = files;
- jqXHR = that._onSend(null, data).then(
- function (result, textStatus, jqXHR) {
- dfd.resolve(result, textStatus, jqXHR);
- },
- function (jqXHR, textStatus, errorThrown) {
- dfd.reject(jqXHR, textStatus, errorThrown);
- }
- );
- }
- );
- return this._enhancePromise(promise);
- }
- data.files = $.makeArray(data.files);
- if (data.files.length) {
- return this._onSend(null, data);
- }
- }
- return this._getXHRPromise(false, data && data.context);
+ _isRegExpOption: function (key, value) {
+ return (
+ key !== 'url' &&
+ $.type(value) === 'string' &&
+ /^\/.*\/[igm]{0,3}$/.test(value)
+ );
+ },
+
+ _initDataAttributes: function () {
+ var that = this,
+ options = this.options,
+ data = this.element.data();
+ // Initialize options set via HTML5 data-attributes:
+ $.each(this.element[0].attributes, function (index, attr) {
+ var key = attr.name.toLowerCase(),
+ value;
+ if (/^data-/.test(key)) {
+ // Convert hyphen-ated key to camelCase:
+ key = key.slice(5).replace(/-[a-z]/g, function (str) {
+ return str.charAt(1).toUpperCase();
+ });
+ value = data[key];
+ if (that._isRegExpOption(key, value)) {
+ value = that._getRegExp(value);
+ }
+ options[key] = value;
}
+ });
+ },
+
+ _create: function () {
+ this._initDataAttributes();
+ this._initSpecialOptions();
+ this._slots = [];
+ this._sequence = this._getXHRPromise(true);
+ this._sending = this._active = 0;
+ this._initProgressObject(this);
+ this._initEventHandlers();
+ },
+
+ // This method is exposed to the widget API and allows to query
+ // the number of active uploads:
+ active: function () {
+ return this._active;
+ },
- });
+ // This method is exposed to the widget API and allows to query
+ // the widget upload progress.
+ // It returns an object with loaded, total and bitrate properties
+ // for the running uploads:
+ progress: function () {
+ return this._progress;
+ },
-}));
+ // This method is exposed to the widget API and allows adding files
+ // using the fileupload API. The data parameter accepts an object which
+ // must have a files property and can contain additional options:
+ // .fileupload('add', {files: filesList});
+ add: function (data) {
+ var that = this;
+ if (!data || this.options.disabled) {
+ return;
+ }
+ if (data.fileInput && !data.files) {
+ this._getFileInputFiles(data.fileInput).always(function (files) {
+ data.files = files;
+ that._onAdd(null, data);
+ });
+ } else {
+ data.files = $.makeArray(data.files);
+ this._onAdd(null, data);
+ }
+ },
+
+ // This method is exposed to the widget API and allows sending files
+ // using the fileupload API. The data parameter accepts an object which
+ // must have a files or fileInput property and can contain additional options:
+ // .fileupload('send', {files: filesList});
+ // The method returns a Promise object for the file upload call.
+ send: function (data) {
+ if (data && !this.options.disabled) {
+ if (data.fileInput && !data.files) {
+ var that = this,
+ dfd = $.Deferred(),
+ promise = dfd.promise(),
+ jqXHR,
+ aborted;
+ promise.abort = function () {
+ aborted = true;
+ if (jqXHR) {
+ return jqXHR.abort();
+ }
+ dfd.reject(null, 'abort', 'abort');
+ return promise;
+ };
+ this._getFileInputFiles(data.fileInput).always(function (files) {
+ if (aborted) {
+ return;
+ }
+ if (!files.length) {
+ dfd.reject();
+ return;
+ }
+ data.files = files;
+ jqXHR = that._onSend(null, data);
+ jqXHR.then(
+ function (result, textStatus, jqXHR) {
+ dfd.resolve(result, textStatus, jqXHR);
+ },
+ function (jqXHR, textStatus, errorThrown) {
+ dfd.reject(jqXHR, textStatus, errorThrown);
+ }
+ );
+ });
+ return this._enhancePromise(promise);
+ }
+ data.files = $.makeArray(data.files);
+ if (data.files.length) {
+ return this._onSend(null, data);
+ }
+ }
+ return this._getXHRPromise(false, data && data.context);
+ }
+ });
+});
diff --git a/lib/web/jquery/fileUploader/jquery.fileuploader.js b/lib/web/jquery/fileUploader/jquery.fileuploader.js
new file mode 100644
index 0000000000000..4ec869ab4f470
--- /dev/null
+++ b/lib/web/jquery/fileUploader/jquery.fileuploader.js
@@ -0,0 +1,33 @@
+/**
+ * Custom Uploader
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+/* global define, require */
+
+(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ 'jquery/fileUploader/jquery.fileupload-image',
+ 'jquery/fileUploader/jquery.fileupload-audio',
+ 'jquery/fileUploader/jquery.fileupload-video',
+ 'jquery/fileUploader/jquery.iframe-transport',
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('jquery/fileUploader/jquery.fileupload-image'),
+ require('jquery/fileUploader/jquery.fileupload-audio'),
+ require('jquery/fileUploader/jquery.fileupload-video'),
+ require('jquery/fileUploader/jquery.iframe-transport')
+ );
+ } else {
+ // Browser globals:
+ factory(window.jQuery);
+ }
+})();
diff --git a/lib/web/jquery/fileUploader/jquery.iframe-transport.js b/lib/web/jquery/fileUploader/jquery.iframe-transport.js
index 4749f46993652..3e3b9a93b05df 100644
--- a/lib/web/jquery/fileUploader/jquery.iframe-transport.js
+++ b/lib/web/jquery/fileUploader/jquery.iframe-transport.js
@@ -1,172 +1,227 @@
/*
- * jQuery Iframe Transport Plugin 1.5
+ * jQuery Iframe Transport Plugin
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
- * http://www.opensource.org/licenses/MIT
+ * https://opensource.org/licenses/MIT
*/
-/*jslint unparam: true, nomen: true */
-/*global define, window, document */
+/* global define, require */
(function (factory) {
- 'use strict';
- if (typeof define === 'function' && define.amd) {
- // Register as an anonymous AMD module:
- define(['jquery'], factory);
- } else {
- // Browser globals:
- factory(window.jQuery);
- }
-}(function ($) {
- 'use strict';
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery'], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(require('jquery'));
+ } else {
+ // Browser globals:
+ factory(window.jQuery);
+ }
+})(function ($) {
+ 'use strict';
- // Helper variable to create unique names for the transport iframes:
- var counter = 0;
+ // Helper variable to create unique names for the transport iframes:
+ var counter = 0,
+ jsonAPI = $,
+ jsonParse = 'parseJSON';
- // The iframe transport accepts three additional options:
- // options.fileInput: a jQuery collection of file input fields
- // options.paramName: the parameter name for the file form data,
- // overrides the name property of the file input field(s),
- // can be a string or an array of strings.
- // options.formData: an array of objects with name and value properties,
- // equivalent to the return data of .serializeArray(), e.g.:
- // [{name: 'a', value: 1}, {name: 'b', value: 2}]
- $.ajaxTransport('iframe', function (options) {
- if (options.async && (options.type === 'POST' || options.type === 'GET')) {
- var form,
- iframe;
- return {
- send: function (_, completeCallback) {
- form = $('');
- form.attr('accept-charset', options.formAcceptCharset);
- // javascript:false as initial iframe src
- // prevents warning popups on HTTPS in IE6.
- // IE versions below IE8 cannot set the name property of
- // elements that have already been added to the DOM,
- // so we set the name along with the iframe HTML markup:
- iframe = $(
- ''
- ).bind('load', function () {
- var fileInputClones,
- paramNames = $.isArray(options.paramName) ?
- options.paramName : [options.paramName];
- iframe
- .unbind('load')
- .bind('load', function () {
- var response;
- // Wrap in a try/catch block to catch exceptions thrown
- // when trying to access cross-domain iframe contents:
- try {
- response = iframe.contents();
- // Google Chrome and Firefox do not throw an
- // exception when calling iframe.contents() on
- // cross-domain requests, so we unify the response:
- if (!response.length || !response[0].firstChild) {
- throw new Error();
- }
- } catch (e) {
- response = undefined;
- }
- // The complete callback returns the
- // iframe content document as response object:
- completeCallback(
- 200,
- 'success',
- {'iframe': response}
- );
- // Fix for IE endless progress bar activity bug
- // (happens on form submits to iframe targets):
- $('')
- .appendTo(form);
- form.remove();
- });
- form
- .prop('target', iframe.prop('name'))
- .prop('action', options.url)
- .prop('method', options.type);
- if (options.formData) {
- $.each(options.formData, function (index, field) {
- $('')
- .prop('name', field.name)
- .val(field.value)
- .appendTo(form);
- });
- }
- if (options.fileInput && options.fileInput.length &&
- options.type === 'POST') {
- fileInputClones = options.fileInput.clone();
- // Insert a clone for each file input field:
- options.fileInput.after(function (index) {
- return fileInputClones[index];
- });
- if (options.paramName) {
- options.fileInput.each(function (index) {
- $(this).prop(
- 'name',
- paramNames[index] || options.paramName
- );
- });
- }
- // Appending the file input fields to the hidden form
- // removes them from their original location:
- form
- .append(options.fileInput)
- .prop('enctype', 'multipart/form-data')
- // enctype must be set as encoding for IE:
- .prop('encoding', 'multipart/form-data');
- }
- form.submit();
- // Insert the file input fields at their original location
- // by replacing the clones with the originals:
- if (fileInputClones && fileInputClones.length) {
- options.fileInput.each(function (index, input) {
- var clone = $(fileInputClones[index]);
- $(input).prop('name', clone.prop('name'));
- clone.replaceWith(input);
- });
- }
- });
- form.append(iframe).appendTo(document.body);
- },
- abort: function () {
- if (iframe) {
- // javascript:false as iframe src aborts the request
- // and prevents warning popups on HTTPS in IE6.
- // concat is used to avoid the "Script URL" JSLint error:
- iframe
- .unbind('load')
- .prop('src', 'javascript'.concat(':false;'));
- }
- if (form) {
- form.remove();
- }
- }
- };
- }
- });
+ if ('JSON' in window && 'parse' in JSON) {
+ jsonAPI = JSON;
+ jsonParse = 'parse';
+ }
- // The iframe transport returns the iframe content document as response.
- // The following adds converters from iframe to text, json, html, and script:
- $.ajaxSetup({
- converters: {
- 'iframe text': function (iframe) {
- return $(iframe[0].body).text();
- },
- 'iframe json': function (iframe) {
- return $.parseJSON($(iframe[0].body).text());
- },
- 'iframe html': function (iframe) {
- return $(iframe[0].body).html();
- },
- 'iframe script': function (iframe) {
- return $.globalEval($(iframe[0].body).text());
+ // The iframe transport accepts four additional options:
+ // options.fileInput: a jQuery collection of file input fields
+ // options.paramName: the parameter name for the file form data,
+ // overrides the name property of the file input field(s),
+ // can be a string or an array of strings.
+ // options.formData: an array of objects with name and value properties,
+ // equivalent to the return data of .serializeArray(), e.g.:
+ // [{name: 'a', value: 1}, {name: 'b', value: 2}]
+ // options.initialIframeSrc: the URL of the initial iframe src,
+ // by default set to "javascript:false;"
+ $.ajaxTransport('iframe', function (options) {
+ if (options.async) {
+ // javascript:false as initial iframe src
+ // prevents warning popups on HTTPS in IE6:
+ // eslint-disable-next-line no-script-url
+ var initialIframeSrc = options.initialIframeSrc || 'javascript:false;',
+ form,
+ iframe,
+ addParamChar;
+ return {
+ send: function (_, completeCallback) {
+ form = $('');
+ form.attr('accept-charset', options.formAcceptCharset);
+ addParamChar = /\?/.test(options.url) ? '&' : '?';
+ // XDomainRequest only supports GET and POST:
+ if (options.type === 'DELETE') {
+ options.url = options.url + addParamChar + '_method=DELETE';
+ options.type = 'POST';
+ } else if (options.type === 'PUT') {
+ options.url = options.url + addParamChar + '_method=PUT';
+ options.type = 'POST';
+ } else if (options.type === 'PATCH') {
+ options.url = options.url + addParamChar + '_method=PATCH';
+ options.type = 'POST';
+ }
+ // IE versions below IE8 cannot set the name property of
+ // elements that have already been added to the DOM,
+ // so we set the name along with the iframe HTML markup:
+ counter += 1;
+ iframe = $(
+ ''
+ ).on('load', function () {
+ var fileInputClones,
+ paramNames = $.isArray(options.paramName)
+ ? options.paramName
+ : [options.paramName];
+ iframe.off('load').on('load', function () {
+ var response;
+ // Wrap in a try/catch block to catch exceptions thrown
+ // when trying to access cross-domain iframe contents:
+ try {
+ response = iframe.contents();
+ // Google Chrome and Firefox do not throw an
+ // exception when calling iframe.contents() on
+ // cross-domain requests, so we unify the response:
+ if (!response.length || !response[0].firstChild) {
+ throw new Error();
+ }
+ } catch (e) {
+ response = undefined;
+ }
+ // The complete callback returns the
+ // iframe content document as response object:
+ completeCallback(200, 'success', { iframe: response });
+ // Fix for IE endless progress bar activity bug
+ // (happens on form submits to iframe targets):
+ $('').appendTo(
+ form
+ );
+ window.setTimeout(function () {
+ // Removing the form in a setTimeout call
+ // allows Chrome's developer tools to display
+ // the response result
+ form.remove();
+ }, 0);
+ });
+ form
+ .prop('target', iframe.prop('name'))
+ .prop('action', options.url)
+ .prop('method', options.type);
+ if (options.formData) {
+ $.each(options.formData, function (index, field) {
+ $('')
+ .prop('name', field.name)
+ .val(field.value)
+ .appendTo(form);
+ });
}
+ if (
+ options.fileInput &&
+ options.fileInput.length &&
+ options.type === 'POST'
+ ) {
+ fileInputClones = options.fileInput.clone();
+ // Insert a clone for each file input field:
+ options.fileInput.after(function (index) {
+ return fileInputClones[index];
+ });
+ if (options.paramName) {
+ options.fileInput.each(function (index) {
+ $(this).prop('name', paramNames[index] || options.paramName);
+ });
+ }
+ // Appending the file input fields to the hidden form
+ // removes them from their original location:
+ form
+ .append(options.fileInput)
+ .prop('enctype', 'multipart/form-data')
+ // enctype must be set as encoding for IE:
+ .prop('encoding', 'multipart/form-data');
+ // Remove the HTML5 form attribute from the input(s):
+ options.fileInput.removeAttr('form');
+ }
+ window.setTimeout(function () {
+ // Submitting the form in a setTimeout call fixes an issue with
+ // Safari 13 not triggering the iframe load event after resetting
+ // the load event handler, see also:
+ // https://github.com/blueimp/jQuery-File-Upload/issues/3633
+ form.submit();
+ // Insert the file input fields at their original location
+ // by replacing the clones with the originals:
+ if (fileInputClones && fileInputClones.length) {
+ options.fileInput.each(function (index, input) {
+ var clone = $(fileInputClones[index]);
+ // Restore the original name and form properties:
+ $(input)
+ .prop('name', clone.prop('name'))
+ .attr('form', clone.attr('form'));
+ clone.replaceWith(input);
+ });
+ }
+ }, 0);
+ });
+ form.append(iframe).appendTo(document.body);
+ },
+ abort: function () {
+ if (iframe) {
+ // javascript:false as iframe src aborts the request
+ // and prevents warning popups on HTTPS in IE6.
+ iframe.off('load').prop('src', initialIframeSrc);
+ }
+ if (form) {
+ form.remove();
+ }
}
- });
+ };
+ }
+ });
-}));
+ // The iframe transport returns the iframe content document as response.
+ // The following adds converters from iframe to text, json, html, xml
+ // and script.
+ // Please note that the Content-Type for JSON responses has to be text/plain
+ // or text/html, if the browser doesn't include application/json in the
+ // Accept header, else IE will show a download dialog.
+ // The Content-Type for XML responses on the other hand has to be always
+ // application/xml or text/xml, so IE properly parses the XML response.
+ // See also
+ // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
+ $.ajaxSetup({
+ converters: {
+ 'iframe text': function (iframe) {
+ return iframe && $(iframe[0].body).text();
+ },
+ 'iframe json': function (iframe) {
+ return iframe && jsonAPI[jsonParse]($(iframe[0].body).text());
+ },
+ 'iframe html': function (iframe) {
+ return iframe && $(iframe[0].body).html();
+ },
+ 'iframe xml': function (iframe) {
+ var xmlDoc = iframe && iframe[0];
+ return xmlDoc && $.isXMLDoc(xmlDoc)
+ ? xmlDoc
+ : $.parseXML(
+ (xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||
+ $(xmlDoc.body).html()
+ );
+ },
+ 'iframe script': function (iframe) {
+ return iframe && $.globalEval($(iframe[0].body).text());
+ }
+ }
+ });
+});
diff --git a/lib/web/jquery/fileUploader/load-image.js b/lib/web/jquery/fileUploader/load-image.js
deleted file mode 100644
index f6817db48c045..0000000000000
--- a/lib/web/jquery/fileUploader/load-image.js
+++ /dev/null
@@ -1 +0,0 @@
-(function(a){"use strict";var b=function(a,c,d){var e=document.createElement("img"),f,g;return e.onerror=c,e.onload=function(){g&&(!d||!d.noRevoke)&&b.revokeObjectURL(g),c(b.scale(e,d))},window.Blob&&a instanceof Blob||window.File&&a instanceof File?f=g=b.createObjectURL(a):f=a,f?(e.src=f,e):b.readFile(a,function(a){e.src=a})},c=window.createObjectURL&&window||window.URL&&URL.revokeObjectURL&&URL||window.webkitURL&&webkitURL;b.scale=function(a,b){b=b||{};var c=document.createElement("canvas"),d=a.width,e=a.height,f=Math.max((b.minWidth||d)/d,(b.minHeight||e)/e);return f>1&&(d=parseInt(d*f,10),e=parseInt(e*f,10)),f=Math.min((b.maxWidth||d)/d,(b.maxHeight||e)/e),f<1&&(d=parseInt(d*f,10),e=parseInt(e*f,10)),a.getContext||b.canvas&&c.getContext?(c.width=d,c.height=e,c.getContext("2d").drawImage(a,0,0,d,e),c):(a.width=d,a.height=e,a)},b.createObjectURL=function(a){return c?c.createObjectURL(a):!1},b.revokeObjectURL=function(a){return c?c.revokeObjectURL(a):!1},b.readFile=function(a,b){if(window.FileReader&&FileReader.prototype.readAsDataURL){var c=new FileReader;return c.onload=function(a){b(a.target.result)},c.readAsDataURL(a),c}return!1},typeof define=="function"&&define.amd?define(function(){return b}):a.loadImage=b})(this);
\ No newline at end of file
diff --git a/lib/web/jquery/fileUploader/locale.js b/lib/web/jquery/fileUploader/locale.js
deleted file mode 100644
index ea64b0a870ff7..0000000000000
--- a/lib/web/jquery/fileUploader/locale.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * jQuery File Upload Plugin Localization Example 6.5.1
- * https://github.com/blueimp/jQuery-File-Upload
- *
- * Copyright 2012, Sebastian Tschan
- * https://blueimp.net
- *
- * Licensed under the MIT license:
- * http://www.opensource.org/licenses/MIT
- */
-
-/*global window */
-
-window.locale = {
- "fileupload": {
- "errors": {
- "maxFileSize": "File is too big",
- "minFileSize": "File is too small",
- "acceptFileTypes": "Filetype not allowed",
- "maxNumberOfFiles": "Max number of files exceeded",
- "uploadedBytes": "Uploaded bytes exceed file size",
- "emptyResult": "Empty file upload result"
- },
- "error": "Error",
- "start": "Start",
- "cancel": "Cancel",
- "destroy": "Delete"
- }
-};
diff --git a/lib/web/jquery/fileUploader/main.js b/lib/web/jquery/fileUploader/main.js
deleted file mode 100644
index 7231899276c4b..0000000000000
--- a/lib/web/jquery/fileUploader/main.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * jQuery File Upload Plugin JS Example 6.7
- * https://github.com/blueimp/jQuery-File-Upload
- *
- * Copyright 2010, Sebastian Tschan
- * https://blueimp.net
- *
- * Licensed under the MIT license:
- * http://www.opensource.org/licenses/MIT
- */
-
-/*jslint nomen: true, unparam: true, regexp: true */
-/*global $, window, document */
-
-$(function () {
- 'use strict';
-
- // Initialize the jQuery File Upload widget:
- $('#fileupload').fileupload();
-
- // Enable iframe cross-domain access via redirect option:
- $('#fileupload').fileupload(
- 'option',
- 'redirect',
- window.location.href.replace(
- /\/[^\/]*$/,
- '/cors/result.html?%s'
- )
- );
-
- if (window.location.hostname === 'blueimp.github.com') {
- // Demo settings:
- $('#fileupload').fileupload('option', {
- url: '//jquery-file-upload.appspot.com/',
- maxFileSize: 5000000,
- acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
- process: [
- {
- action: 'load',
- fileTypes: /^image\/(gif|jpeg|png)$/,
- maxFileSize: 20000000 // 20MB
- },
- {
- action: 'resize',
- maxWidth: 1440,
- maxHeight: 900
- },
- {
- action: 'save'
- }
- ]
- });
- // Upload server status check for browsers with CORS support:
- if ($.support.cors) {
- $.ajax({
- url: '//jquery-file-upload.appspot.com/',
- type: 'HEAD'
- }).fail(function () {
- $('')
- .text($.mage.__('Upload server currently unavailable - ') +
- new Date())
- .appendTo('#fileupload');
- });
- }
- } else {
- // Load existing files:
- $('#fileupload').each(function () {
- var that = this;
- $.getJSON(this.action, function (result) {
- if (result && result.length) {
- $(that).fileupload('option', 'done')
- .call(that, null, {result: result});
- }
- });
- });
- }
-
-});
diff --git a/lib/web/jquery/fileUploader/vendor/blueimp-canvas-to-blob/LICENSE.txt b/lib/web/jquery/fileUploader/vendor/blueimp-canvas-to-blob/LICENSE.txt
new file mode 100644
index 0000000000000..e1ad73662d4a5
--- /dev/null
+++ b/lib/web/jquery/fileUploader/vendor/blueimp-canvas-to-blob/LICENSE.txt
@@ -0,0 +1,20 @@
+MIT License
+
+Copyright © 2012 Sebastian Tschan, https://blueimp.net
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/lib/web/jquery/fileUploader/vendor/blueimp-canvas-to-blob/README.md b/lib/web/jquery/fileUploader/vendor/blueimp-canvas-to-blob/README.md
new file mode 100644
index 0000000000000..92e16c63ce7cd
--- /dev/null
+++ b/lib/web/jquery/fileUploader/vendor/blueimp-canvas-to-blob/README.md
@@ -0,0 +1,135 @@
+# JavaScript Canvas to Blob
+
+## Contents
+
+- [Description](#description)
+- [Setup](#setup)
+- [Usage](#usage)
+- [Requirements](#requirements)
+- [Browsers](#browsers)
+- [API](#api)
+- [Test](#test)
+- [License](#license)
+
+## Description
+
+Canvas to Blob is a
+[polyfill](https://developer.mozilla.org/en-US/docs/Glossary/Polyfill) for
+Browsers that don't support the standard JavaScript
+[HTMLCanvasElement.toBlob](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob)
+method.
+
+It can be used to create
+[Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) objects from an
+HTML [canvas](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas)
+element.
+
+## Setup
+
+Install via [NPM](https://www.npmjs.com/package/blueimp-canvas-to-blob):
+
+```sh
+npm install blueimp-canvas-to-blob
+```
+
+This will install the JavaScript files inside
+`./node_modules/blueimp-canvas-to-blob/js/` relative to your current directory,
+from where you can copy them into a folder that is served by your web server.
+
+Next include the minified JavaScript Canvas to Blob script in your HTML markup:
+
+```html
+
+```
+
+Or alternatively, include the non-minified version:
+
+```html
+
+```
+
+## Usage
+
+You can use the `canvas.toBlob()` method in the same way as the native
+implementation:
+
+```js
+var canvas = document.createElement('canvas')
+// Edit the canvas ...
+if (canvas.toBlob) {
+ canvas.toBlob(function (blob) {
+ // Do something with the blob object,
+ // e.g. create multipart form data for file uploads:
+ var formData = new FormData()
+ formData.append('file', blob, 'image.jpg')
+ // ...
+ }, 'image/jpeg')
+}
+```
+
+## Requirements
+
+The JavaScript Canvas to Blob function has zero dependencies.
+
+However, it is a very suitable complement to the
+[JavaScript Load Image](https://github.com/blueimp/JavaScript-Load-Image)
+function.
+
+## Browsers
+
+The following browsers have native support for
+[HTMLCanvasElement.toBlob](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob):
+
+- Chrome 50+
+- Firefox 19+
+- Safari 11+
+- Mobile Chrome 50+ (Android)
+- Mobile Firefox 4+ (Android)
+- Mobile Safari 11+ (iOS)
+- Edge 79+
+
+Browsers which implement the following APIs support `canvas.toBlob()` via
+polyfill:
+
+- [HTMLCanvasElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement)
+- [HTMLCanvasElement.toDataURL](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL)
+- [Blob() constructor](https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob)
+- [atob](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/atob)
+- [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)
+- [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)
+
+This includes the following browsers:
+
+- Chrome 20+
+- Firefox 13+
+- Safari 8+
+- Mobile Chrome 25+ (Android)
+- Mobile Firefox 14+ (Android)
+- Mobile Safari 8+ (iOS)
+- Edge 74+
+- Edge Legacy 12+
+- Internet Explorer 10+
+
+## API
+
+In addition to the `canvas.toBlob()` polyfill, the JavaScript Canvas to Blob
+script exposes its helper function `dataURLtoBlob(url)`:
+
+```js
+// Uncomment the following line when using a module loader like webpack:
+// var dataURLtoBlob = require('blueimp-canvas-to-blob')
+
+// black+white 3x2 GIF, base64 data:
+var b64 = 'R0lGODdhAwACAPEAAAAAAP///yZFySZFySH5BAEAAAIALAAAAAADAAIAAAIDRAJZADs='
+var url = 'data:image/gif;base64,' + b64
+var blob = dataURLtoBlob(url)
+```
+
+## Test
+
+[Unit tests](https://blueimp.github.io/JavaScript-Canvas-to-Blob/test/)
+
+## License
+
+The JavaScript Canvas to Blob script is released under the
+[MIT license](https://opensource.org/licenses/MIT).
diff --git a/lib/web/jquery/fileUploader/vendor/blueimp-canvas-to-blob/js/canvas-to-blob.js b/lib/web/jquery/fileUploader/vendor/blueimp-canvas-to-blob/js/canvas-to-blob.js
new file mode 100644
index 0000000000000..8cd717bc1205f
--- /dev/null
+++ b/lib/web/jquery/fileUploader/vendor/blueimp-canvas-to-blob/js/canvas-to-blob.js
@@ -0,0 +1,143 @@
+/*
+ * JavaScript Canvas to Blob
+ * https://github.com/blueimp/JavaScript-Canvas-to-Blob
+ *
+ * Copyright 2012, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ *
+ * Based on stackoverflow user Stoive's code snippet:
+ * http://stackoverflow.com/q/4998908
+ */
+
+/* global define, Uint8Array, ArrayBuffer, module */
+
+;(function (window) {
+ 'use strict'
+
+ var CanvasPrototype =
+ window.HTMLCanvasElement && window.HTMLCanvasElement.prototype
+ var hasBlobConstructor =
+ window.Blob &&
+ (function () {
+ try {
+ return Boolean(new Blob())
+ } catch (e) {
+ return false
+ }
+ })()
+ var hasArrayBufferViewSupport =
+ hasBlobConstructor &&
+ window.Uint8Array &&
+ (function () {
+ try {
+ return new Blob([new Uint8Array(100)]).size === 100
+ } catch (e) {
+ return false
+ }
+ })()
+ var BlobBuilder =
+ window.BlobBuilder ||
+ window.WebKitBlobBuilder ||
+ window.MozBlobBuilder ||
+ window.MSBlobBuilder
+ var dataURIPattern = /^data:((.*?)(;charset=.*?)?)(;base64)?,/
+ var dataURLtoBlob =
+ (hasBlobConstructor || BlobBuilder) &&
+ window.atob &&
+ window.ArrayBuffer &&
+ window.Uint8Array &&
+ function (dataURI) {
+ var matches,
+ mediaType,
+ isBase64,
+ dataString,
+ byteString,
+ arrayBuffer,
+ intArray,
+ i,
+ bb
+ // Parse the dataURI components as per RFC 2397
+ matches = dataURI.match(dataURIPattern)
+ if (!matches) {
+ throw new Error('invalid data URI')
+ }
+ // Default to text/plain;charset=US-ASCII
+ mediaType = matches[2]
+ ? matches[1]
+ : 'text/plain' + (matches[3] || ';charset=US-ASCII')
+ isBase64 = !!matches[4]
+ dataString = dataURI.slice(matches[0].length)
+ if (isBase64) {
+ // Convert base64 to raw binary data held in a string:
+ byteString = atob(dataString)
+ } else {
+ // Convert base64/URLEncoded data component to raw binary:
+ byteString = decodeURIComponent(dataString)
+ }
+ // Write the bytes of the string to an ArrayBuffer:
+ arrayBuffer = new ArrayBuffer(byteString.length)
+ intArray = new Uint8Array(arrayBuffer)
+ for (i = 0; i < byteString.length; i += 1) {
+ intArray[i] = byteString.charCodeAt(i)
+ }
+ // Write the ArrayBuffer (or ArrayBufferView) to a blob:
+ if (hasBlobConstructor) {
+ return new Blob([hasArrayBufferViewSupport ? intArray : arrayBuffer], {
+ type: mediaType
+ })
+ }
+ bb = new BlobBuilder()
+ bb.append(arrayBuffer)
+ return bb.getBlob(mediaType)
+ }
+ if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
+ if (CanvasPrototype.mozGetAsFile) {
+ CanvasPrototype.toBlob = function (callback, type, quality) {
+ var self = this
+ setTimeout(function () {
+ if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
+ callback(dataURLtoBlob(self.toDataURL(type, quality)))
+ } else {
+ callback(self.mozGetAsFile('blob', type))
+ }
+ })
+ }
+ } else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
+ if (CanvasPrototype.msToBlob) {
+ CanvasPrototype.toBlob = function (callback, type, quality) {
+ var self = this
+ setTimeout(function () {
+ if (
+ ((type && type !== 'image/png') || quality) &&
+ CanvasPrototype.toDataURL &&
+ dataURLtoBlob
+ ) {
+ callback(dataURLtoBlob(self.toDataURL(type, quality)))
+ } else {
+ callback(self.msToBlob(type))
+ }
+ })
+ }
+ } else {
+ CanvasPrototype.toBlob = function (callback, type, quality) {
+ var self = this
+ setTimeout(function () {
+ callback(dataURLtoBlob(self.toDataURL(type, quality)))
+ })
+ }
+ }
+ }
+ }
+ if (typeof define === 'function' && define.amd) {
+ define(function () {
+ return dataURLtoBlob
+ })
+ } else if (typeof module === 'object' && module.exports) {
+ module.exports = dataURLtoBlob
+ } else {
+ window.dataURLtoBlob = dataURLtoBlob
+ }
+})(window)
diff --git a/lib/web/jquery/fileUploader/vendor/blueimp-load-image/LICENSE.txt b/lib/web/jquery/fileUploader/vendor/blueimp-load-image/LICENSE.txt
new file mode 100644
index 0000000000000..d6a9d74758be3
--- /dev/null
+++ b/lib/web/jquery/fileUploader/vendor/blueimp-load-image/LICENSE.txt
@@ -0,0 +1,20 @@
+MIT License
+
+Copyright © 2011 Sebastian Tschan, https://blueimp.net
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/lib/web/jquery/fileUploader/vendor/blueimp-load-image/README.md b/lib/web/jquery/fileUploader/vendor/blueimp-load-image/README.md
new file mode 100644
index 0000000000000..5759a126aa172
--- /dev/null
+++ b/lib/web/jquery/fileUploader/vendor/blueimp-load-image/README.md
@@ -0,0 +1,1070 @@
+# JavaScript Load Image
+
+> A JavaScript library to load and transform image files.
+
+## Contents
+
+- [Demo](https://blueimp.github.io/JavaScript-Load-Image/)
+- [Description](#description)
+- [Setup](#setup)
+- [Usage](#usage)
+ - [Image loading](#image-loading)
+ - [Image scaling](#image-scaling)
+- [Requirements](#requirements)
+- [Browser support](#browser-support)
+- [API](#api)
+ - [Callback](#callback)
+ - [Function signature](#function-signature)
+ - [Cancel image loading](#cancel-image-loading)
+ - [Callback arguments](#callback-arguments)
+ - [Error handling](#error-handling)
+ - [Promise](#promise)
+- [Options](#options)
+ - [maxWidth](#maxwidth)
+ - [maxHeight](#maxheight)
+ - [minWidth](#minwidth)
+ - [minHeight](#minheight)
+ - [sourceWidth](#sourcewidth)
+ - [sourceHeight](#sourceheight)
+ - [top](#top)
+ - [right](#right)
+ - [bottom](#bottom)
+ - [left](#left)
+ - [contain](#contain)
+ - [cover](#cover)
+ - [aspectRatio](#aspectratio)
+ - [pixelRatio](#pixelratio)
+ - [downsamplingRatio](#downsamplingratio)
+ - [imageSmoothingEnabled](#imagesmoothingenabled)
+ - [imageSmoothingQuality](#imagesmoothingquality)
+ - [crop](#crop)
+ - [orientation](#orientation)
+ - [meta](#meta)
+ - [canvas](#canvas)
+ - [crossOrigin](#crossorigin)
+ - [noRevoke](#norevoke)
+- [Metadata parsing](#metadata-parsing)
+ - [Image head](#image-head)
+ - [Exif parser](#exif-parser)
+ - [Exif Thumbnail](#exif-thumbnail)
+ - [Exif IFD](#exif-ifd)
+ - [GPSInfo IFD](#gpsinfo-ifd)
+ - [Interoperability IFD](#interoperability-ifd)
+ - [Exif parser options](#exif-parser-options)
+ - [Exif writer](#exif-writer)
+ - [IPTC parser](#iptc-parser)
+ - [IPTC parser options](#iptc-parser-options)
+- [License](#license)
+- [Credits](#credits)
+
+## Description
+
+JavaScript Load Image is a library to load images provided as `File` or `Blob`
+objects or via `URL`. It returns an optionally **scaled**, **cropped** or
+**rotated** HTML `img` or `canvas` element.
+
+It also provides methods to parse image metadata to extract
+[IPTC](https://iptc.org/standards/photo-metadata/) and
+[Exif](https://en.wikipedia.org/wiki/Exif) tags as well as embedded thumbnail
+images, to overwrite the Exif Orientation value and to restore the complete
+image header after resizing.
+
+## Setup
+
+Install via [NPM](https://www.npmjs.com/package/blueimp-load-image):
+
+```sh
+npm install blueimp-load-image
+```
+
+This will install the JavaScript files inside
+`./node_modules/blueimp-load-image/js/` relative to your current directory, from
+where you can copy them into a folder that is served by your web server.
+
+Next include the combined and minified JavaScript Load Image script in your HTML
+markup:
+
+```html
+
+```
+
+Or alternatively, choose which components you want to include:
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## Usage
+
+### Image loading
+
+In your application code, use the `loadImage()` function with
+[callback](#callback) style:
+
+```js
+document.getElementById('file-input').onchange = function () {
+ loadImage(
+ this.files[0],
+ function (img) {
+ document.body.appendChild(img)
+ },
+ { maxWidth: 600 } // Options
+ )
+}
+```
+
+Or use the [Promise](#promise) based API like this ([requires](#requirements) a
+polyfill for older browsers):
+
+```js
+document.getElementById('file-input').onchange = function () {
+ loadImage(this.files[0], { maxWidth: 600 }).then(function (data) {
+ document.body.appendChild(data.image)
+ })
+}
+```
+
+With
+[async/await](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await)
+(requires a modern browser or a code transpiler like
+[Babel](https://babeljs.io/) or [TypeScript](https://www.typescriptlang.org/)):
+
+```js
+document.getElementById('file-input').onchange = async function () {
+ let data = await loadImage(this.files[0], { maxWidth: 600 })
+ document.body.appendChild(data.image)
+}
+```
+
+### Image scaling
+
+It is also possible to use the image scaling functionality directly with an
+existing image:
+
+```js
+var scaledImage = loadImage.scale(
+ img, // img or canvas element
+ { maxWidth: 600 }
+)
+```
+
+## Requirements
+
+The JavaScript Load Image library has zero dependencies, but benefits from the
+following two
+[polyfills](https://developer.mozilla.org/en-US/docs/Glossary/Polyfill):
+
+- [blueimp-canvas-to-blob](https://github.com/blueimp/JavaScript-Canvas-to-Blob)
+ for browsers without native
+ [HTMLCanvasElement.toBlob](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob)
+ support, to create `Blob` objects out of `canvas` elements.
+- [promise-polyfill](https://github.com/taylorhakes/promise-polyfill) to be able
+ to use the
+ [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
+ based `loadImage` API in Browsers without native `Promise` support.
+
+## Browser support
+
+Browsers which implement the following APIs support all options:
+
+- Loading images from File and Blob objects:
+ - [URL.createObjectURL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL)
+ or
+ [FileReader.readAsDataURL](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL)
+- Parsing meta data:
+ - [FileReader.readAsArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsArrayBuffer)
+ - [Blob.slice](https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice)
+ - [DataView](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView)
+ (no [BigInt](https://developer.mozilla.org/en-US/docs/Glossary/BigInt)
+ support required)
+- Parsing meta data from images loaded via URL:
+ - [fetch Response.blob](https://developer.mozilla.org/en-US/docs/Web/API/Body/blob)
+ or
+ [XMLHttpRequest.responseType blob](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType#blob)
+- Promise based API:
+ - [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
+
+This includes (but is not limited to) the following browsers:
+
+- Chrome 32+
+- Firefox 29+
+- Safari 8+
+- Mobile Chrome 42+ (Android)
+- Mobile Firefox 50+ (Android)
+- Mobile Safari 8+ (iOS)
+- Edge 74+
+- Edge Legacy 12+
+- Internet Explorer 10+ `*`
+
+`*` Internet Explorer [requires](#requirements) a polyfill for the `Promise`
+based API.
+
+Loading an image from a URL and applying transformations (scaling, cropping and
+rotating - except `orientation:true`, which requires reading meta data) is
+supported by all browsers which implement the
+[HTMLCanvasElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement)
+interface.
+
+Loading an image from a URL and scaling it in size is supported by all browsers
+which implement the
+[img](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img) element and
+has been tested successfully with browser engines as old as Internet Explorer 5
+(via
+[IE11's emulation mode]()).
+
+The `loadImage()` function applies options using
+[progressive enhancement](https://en.wikipedia.org/wiki/Progressive_enhancement)
+and falls back to a configuration that is supported by the browser, e.g. if the
+`canvas` element is not supported, an equivalent `img` element is returned.
+
+## API
+
+### Callback
+
+#### Function signature
+
+The `loadImage()` function accepts a
+[File](https://developer.mozilla.org/en-US/docs/Web/API/File) or
+[Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) object or an image
+URL as first argument.
+
+If a [File](https://developer.mozilla.org/en-US/docs/Web/API/File) or
+[Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) is passed as
+parameter, it returns an HTML `img` element if the browser supports the
+[URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) API, alternatively a
+[FileReader](https://developer.mozilla.org/en-US/docs/Web/API/FileReader) object
+if the `FileReader` API is supported, or `false`.
+
+It always returns an HTML
+[img](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Img) element
+when passing an image URL:
+
+```js
+var loadingImage = loadImage(
+ 'https://example.org/image.png',
+ function (img) {
+ document.body.appendChild(img)
+ },
+ { maxWidth: 600 }
+)
+```
+
+#### Cancel image loading
+
+Some browsers (e.g. Chrome) will cancel the image loading process if the `src`
+property of an `img` element is changed.
+To avoid unnecessary requests, we can use the
+[data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs)
+of a 1x1 pixel transparent GIF image as `src` target to cancel the original
+image download.
+
+To disable callback handling, we can also unset the image event handlers and for
+maximum browser compatibility, cancel the file reading process if the returned
+object is a
+[FileReader](https://developer.mozilla.org/en-US/docs/Web/API/FileReader)
+instance:
+
+```js
+var loadingImage = loadImage(
+ 'https://example.org/image.png',
+ function (img) {
+ document.body.appendChild(img)
+ },
+ { maxWidth: 600 }
+)
+
+if (loadingImage) {
+ // Unset event handling for the loading image:
+ loadingImage.onload = loadingImage.onerror = null
+
+ // Cancel image loading process:
+ if (loadingImage.abort) {
+ // FileReader instance, stop the file reading process:
+ loadingImage.abort()
+ } else {
+ // HTMLImageElement element, cancel the original image request by changing
+ // the target source to the data URL of a 1x1 pixel transparent image GIF:
+ loadingImage.src =
+ 'data:image/gif;base64,' +
+ 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
+ }
+}
+```
+
+**Please note:**
+The `img` element (or `FileReader` instance) for the loading image is only
+returned when using the callback style API and not available with the
+[Promise](#promise) based API.
+
+#### Callback arguments
+
+For the callback style API, the second argument to `loadImage()` must be a
+`callback` function, which is called when the image has been loaded or an error
+occurred while loading the image.
+
+The callback function is passed two arguments:
+
+1. An HTML [img](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img)
+ element or
+ [canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)
+ element, or an
+ [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event) object of
+ type `error`.
+2. An object with the original image dimensions as properties and potentially
+ additional [metadata](#metadata-parsing).
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ document.body.appendChild(img)
+ console.log('Original image width: ', data.originalWidth)
+ console.log('Original image height: ', data.originalHeight)
+ },
+ { maxWidth: 600, meta: true }
+)
+```
+
+**Please note:**
+The original image dimensions reflect the natural width and height of the loaded
+image before applying any transformation.
+For consistent values across browsers, [metadata](#metadata-parsing) parsing has
+to be enabled via `meta:true`, so `loadImage` can detect automatic image
+orientation and normalize the dimensions.
+
+#### Error handling
+
+Example code implementing error handling:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ if (img.type === 'error') {
+ console.error('Error loading image file')
+ } else {
+ document.body.appendChild(img)
+ }
+ },
+ { maxWidth: 600 }
+)
+```
+
+### Promise
+
+If the `loadImage()` function is called without a `callback` function as second
+argument and the
+[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
+API is available, it returns a `Promise` object:
+
+```js
+loadImage(fileOrBlobOrUrl, { maxWidth: 600, meta: true })
+ .then(function (data) {
+ document.body.appendChild(data.image)
+ console.log('Original image width: ', data.originalWidth)
+ console.log('Original image height: ', data.originalHeight)
+ })
+ .catch(function (err) {
+ // Handling image loading errors
+ console.log(err)
+ })
+```
+
+The `Promise` resolves with an object with the following properties:
+
+- `image`: An HTML
+ [img](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img) or
+ [canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) element.
+- `originalWidth`: The original width of the image.
+- `originalHeight`: The original height of the image.
+
+Please also read the note about original image dimensions normalization in the
+[callback arguments](#callback-arguments) section.
+
+If [metadata](#metadata-parsing) has been parsed, additional properties might be
+present on the object.
+
+If image loading fails, the `Promise` rejects with an
+[Event](https://developer.mozilla.org/en-US/docs/Web/API/Event) object of type
+`error`.
+
+## Options
+
+The optional options argument to `loadImage()` allows to configure the image
+loading.
+
+It can be used the following way with the callback style:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img) {
+ document.body.appendChild(img)
+ },
+ {
+ maxWidth: 600,
+ maxHeight: 300,
+ minWidth: 100,
+ minHeight: 50,
+ canvas: true
+ }
+)
+```
+
+Or the following way with the `Promise` based API:
+
+```js
+loadImage(fileOrBlobOrUrl, {
+ maxWidth: 600,
+ maxHeight: 300,
+ minWidth: 100,
+ minHeight: 50,
+ canvas: true
+}).then(function (data) {
+ document.body.appendChild(data.image)
+})
+```
+
+All settings are optional. By default, the image is returned as HTML `img`
+element without any image size restrictions.
+
+### maxWidth
+
+Defines the maximum width of the `img`/`canvas` element.
+
+### maxHeight
+
+Defines the maximum height of the `img`/`canvas` element.
+
+### minWidth
+
+Defines the minimum width of the `img`/`canvas` element.
+
+### minHeight
+
+Defines the minimum height of the `img`/`canvas` element.
+
+### sourceWidth
+
+The width of the sub-rectangle of the source image to draw into the destination
+canvas.
+Defaults to the source image width and requires `canvas: true`.
+
+### sourceHeight
+
+The height of the sub-rectangle of the source image to draw into the destination
+canvas.
+Defaults to the source image height and requires `canvas: true`.
+
+### top
+
+The top margin of the sub-rectangle of the source image.
+Defaults to `0` and requires `canvas: true`.
+
+### right
+
+The right margin of the sub-rectangle of the source image.
+Defaults to `0` and requires `canvas: true`.
+
+### bottom
+
+The bottom margin of the sub-rectangle of the source image.
+Defaults to `0` and requires `canvas: true`.
+
+### left
+
+The left margin of the sub-rectangle of the source image.
+Defaults to `0` and requires `canvas: true`.
+
+### contain
+
+Scales the image up/down to contain it in the max dimensions if set to `true`.
+This emulates the CSS feature
+[background-image: contain](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Backgrounds_and_Borders/Resizing_background_images#contain).
+
+### cover
+
+Scales the image up/down to cover the max dimensions with the image dimensions
+if set to `true`.
+This emulates the CSS feature
+[background-image: cover](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Backgrounds_and_Borders/Resizing_background_images#cover).
+
+### aspectRatio
+
+Crops the image to the given aspect ratio (e.g. `16/9`).
+Setting the `aspectRatio` also enables the `crop` option.
+
+### pixelRatio
+
+Defines the ratio of the canvas pixels to the physical image pixels on the
+screen.
+Should be set to
+[window.devicePixelRatio](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio)
+unless the scaled image is not rendered on screen.
+Defaults to `1` and requires `canvas: true`.
+
+### downsamplingRatio
+
+Defines the ratio in which the image is downsampled (scaled down in steps).
+By default, images are downsampled in one step.
+With a ratio of `0.5`, each step scales the image to half the size, before
+reaching the target dimensions.
+Requires `canvas: true`.
+
+### imageSmoothingEnabled
+
+If set to `false`,
+[disables image smoothing](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled).
+Defaults to `true` and requires `canvas: true`.
+
+### imageSmoothingQuality
+
+Sets the
+[quality of image smoothing](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingQuality).
+Possible values: `'low'`, `'medium'`, `'high'`
+Defaults to `'low'` and requires `canvas: true`.
+
+### crop
+
+Crops the image to the `maxWidth`/`maxHeight` constraints if set to `true`.
+Enabling the `crop` option also enables the `canvas` option.
+
+### orientation
+
+Transform the canvas according to the specified Exif orientation, which can be
+an `integer` in the range of `1` to `8` or the boolean value `true`.
+
+When set to `true`, it will set the orientation value based on the Exif data of
+the image, which will be parsed automatically if the Exif extension is
+available.
+
+Exif orientation values to correctly display the letter F:
+
+```
+ 1 2
+ ██████ ██████
+ ██ ██
+ ████ ████
+ ██ ██
+ ██ ██
+
+ 3 4
+ ██ ██
+ ██ ██
+ ████ ████
+ ██ ██
+ ██████ ██████
+
+ 5 6
+██████████ ██
+██ ██ ██ ██
+██ ██████████
+
+ 7 8
+ ██ ██████████
+ ██ ██ ██ ██
+██████████ ██
+```
+
+Setting `orientation` to `true` enables the `canvas` and `meta` options, unless
+the browser supports automatic image orientation (see
+[browser support for image-orientation](https://caniuse.com/#feat=css-image-orientation)).
+
+Setting `orientation` to `1` enables the `canvas` and `meta` options if the
+browser does support automatic image orientation (to allow reset of the
+orientation).
+
+Setting `orientation` to an integer in the range of `2` to `8` always enables
+the `canvas` option and also enables the `meta` option if the browser supports
+automatic image orientation (again to allow reset).
+
+### meta
+
+Automatically parses the image metadata if set to `true`.
+
+If metadata has been found, the data object passed as second argument to the
+callback function has additional properties (see
+[metadata parsing](#metadata-parsing)).
+
+If the file is given as URL and the browser supports the
+[fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) or the
+XHR
+[responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType)
+`blob`, fetches the file as `Blob` to be able to parse the metadata.
+
+### canvas
+
+Returns the image as
+[canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) element if
+set to `true`.
+
+### crossOrigin
+
+Sets the `crossOrigin` property on the `img` element for loading
+[CORS enabled images](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image).
+
+### noRevoke
+
+By default, the
+[created object URL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL)
+is revoked after the image has been loaded, except when this option is set to
+`true`.
+
+## Metadata parsing
+
+If the Load Image Meta extension is included, it is possible to parse image meta
+data automatically with the `meta` option:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ console.log('Original image head: ', data.imageHead)
+ console.log('Exif data: ', data.exif) // requires exif extension
+ console.log('IPTC data: ', data.iptc) // requires iptc extension
+ },
+ { meta: true }
+)
+```
+
+Or alternatively via `loadImage.parseMetaData`, which can be used with an
+available `File` or `Blob` object as first argument:
+
+```js
+loadImage.parseMetaData(
+ fileOrBlob,
+ function (data) {
+ console.log('Original image head: ', data.imageHead)
+ console.log('Exif data: ', data.exif) // requires exif extension
+ console.log('IPTC data: ', data.iptc) // requires iptc extension
+ },
+ {
+ maxMetaDataSize: 262144
+ }
+)
+```
+
+Or using the
+[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
+based API:
+
+```js
+loadImage
+ .parseMetaData(fileOrBlob, {
+ maxMetaDataSize: 262144
+ })
+ .then(function (data) {
+ console.log('Original image head: ', data.imageHead)
+ console.log('Exif data: ', data.exif) // requires exif extension
+ console.log('IPTC data: ', data.iptc) // requires iptc extension
+ })
+```
+
+The Metadata extension adds additional options used for the `parseMetaData`
+method:
+
+- `maxMetaDataSize`: Maximum number of bytes of metadata to parse.
+- `disableImageHead`: Disable parsing the original image head.
+- `disableMetaDataParsers`: Disable parsing metadata (image head only)
+
+### Image head
+
+Resized JPEG images can be combined with their original image head via
+`loadImage.replaceHead`, which requires the resized image as `Blob` object as
+first argument and an `ArrayBuffer` image head as second argument.
+
+With callback style, the third argument must be a `callback` function, which is
+called with the new `Blob` object:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ if (data.imageHead) {
+ img.toBlob(function (blob) {
+ loadImage.replaceHead(blob, data.imageHead, function (newBlob) {
+ // do something with the new Blob object
+ })
+ }, 'image/jpeg')
+ }
+ },
+ { meta: true, canvas: true, maxWidth: 800 }
+)
+```
+
+Or using the
+[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
+based API like this:
+
+```js
+loadImage(fileOrBlobOrUrl, { meta: true, canvas: true, maxWidth: 800 })
+ .then(function (data) {
+ if (!data.imageHead) throw new Error('Could not parse image metadata')
+ return new Promise(function (resolve) {
+ data.image.toBlob(function (blob) {
+ data.blob = blob
+ resolve(data)
+ }, 'image/jpeg')
+ })
+ })
+ .then(function (data) {
+ return loadImage.replaceHead(data.blob, data.imageHead)
+ })
+ .then(function (blob) {
+ // do something with the new Blob object
+ })
+ .catch(function (err) {
+ console.error(err)
+ })
+```
+
+**Please note:**
+`Blob` objects of resized images can be created via
+[HTMLCanvasElement.toBlob](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob).
+[blueimp-canvas-to-blob](https://github.com/blueimp/JavaScript-Canvas-to-Blob)
+provides a polyfill for browsers without native `canvas.toBlob()` support.
+
+### Exif parser
+
+If you include the Load Image Exif Parser extension, the argument passed to the
+callback for `parseMetaData` will contain the following additional properties if
+Exif data could be found in the given image:
+
+- `exif`: The parsed Exif tags
+- `exifOffsets`: The parsed Exif tag offsets
+- `exifTiffOffset`: TIFF header offset (used for offset pointers)
+- `exifLittleEndian`: little endian order if true, big endian if false
+
+The `exif` object stores the parsed Exif tags:
+
+```js
+var orientation = data.exif[0x0112] // Orientation
+```
+
+The `exif` and `exifOffsets` objects also provide a `get()` method to retrieve
+the tag value/offset via the tag's mapped name:
+
+```js
+var orientation = data.exif.get('Orientation')
+var orientationOffset = data.exifOffsets.get('Orientation')
+```
+
+By default, only the following names are mapped:
+
+- `Orientation`
+- `Thumbnail` (see [Exif Thumbnail](#exif-thumbnail))
+- `Exif` (see [Exif IFD](#exif-ifd))
+- `GPSInfo` (see [GPSInfo IFD](#gpsinfo-ifd))
+- `Interoperability` (see [Interoperability IFD](#interoperability-ifd))
+
+If you also include the Load Image Exif Map library, additional tag mappings
+become available, as well as three additional methods:
+
+- `exif.getText()`
+- `exif.getName()`
+- `exif.getAll()`
+
+```js
+var orientationText = data.exif.getText('Orientation') // e.g. "Rotate 90° CW"
+
+var name = data.exif.getName(0x0112) // "Orientation"
+
+// A map of all parsed tags with their mapped names/text as keys/values:
+var allTags = data.exif.getAll()
+```
+
+#### Exif Thumbnail
+
+Example code displaying a thumbnail image embedded into the Exif metadata:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ var exif = data.exif
+ var thumbnail = exif && exif.get('Thumbnail')
+ var blob = thumbnail && thumbnail.get('Blob')
+ if (blob) {
+ loadImage(
+ blob,
+ function (thumbImage) {
+ document.body.appendChild(thumbImage)
+ },
+ { orientation: exif.get('Orientation') }
+ )
+ }
+ },
+ { meta: true }
+)
+```
+
+#### Exif IFD
+
+Example code displaying data from the Exif IFD (Image File Directory) that
+contains Exif specified TIFF tags:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ var exifIFD = data.exif && data.exif.get('Exif')
+ if (exifIFD) {
+ // Map of all Exif IFD tags with their mapped names/text as keys/values:
+ console.log(exifIFD.getAll())
+ // A specific Exif IFD tag value:
+ console.log(exifIFD.get('UserComment'))
+ }
+ },
+ { meta: true }
+)
+```
+
+#### GPSInfo IFD
+
+Example code displaying data from the Exif IFD (Image File Directory) that
+contains [GPS](https://en.wikipedia.org/wiki/Global_Positioning_System) info:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ var gpsInfo = data.exif && data.exif.get('GPSInfo')
+ if (gpsInfo) {
+ // Map of all GPSInfo tags with their mapped names/text as keys/values:
+ console.log(gpsInfo.getAll())
+ // A specific GPSInfo tag value:
+ console.log(gpsInfo.get('GPSLatitude'))
+ }
+ },
+ { meta: true }
+)
+```
+
+#### Interoperability IFD
+
+Example code displaying data from the Exif IFD (Image File Directory) that
+contains Interoperability data:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ var interoperabilityData = data.exif && data.exif.get('Interoperability')
+ if (interoperabilityData) {
+ // The InteroperabilityIndex tag value:
+ console.log(interoperabilityData.get('InteroperabilityIndex'))
+ }
+ },
+ { meta: true }
+)
+```
+
+#### Exif parser options
+
+The Exif parser adds additional options:
+
+- `disableExif`: Disables Exif parsing when `true`.
+- `disableExifOffsets`: Disables storing Exif tag offsets when `true`.
+- `includeExifTags`: A map of Exif tags to include for parsing (includes all but
+ the excluded tags by default).
+- `excludeExifTags`: A map of Exif tags to exclude from parsing (defaults to
+ exclude `Exif` `MakerNote`).
+
+An example parsing only Orientation, Thumbnail and ExifVersion tags:
+
+```js
+loadImage.parseMetaData(
+ fileOrBlob,
+ function (data) {
+ console.log('Exif data: ', data.exif)
+ },
+ {
+ includeExifTags: {
+ 0x0112: true, // Orientation
+ ifd1: {
+ 0x0201: true, // JPEGInterchangeFormat (Thumbnail data offset)
+ 0x0202: true // JPEGInterchangeFormatLength (Thumbnail data length)
+ },
+ 0x8769: {
+ // ExifIFDPointer
+ 0x9000: true // ExifVersion
+ }
+ }
+ }
+)
+```
+
+An example excluding `Exif` `MakerNote` and `GPSInfo`:
+
+```js
+loadImage.parseMetaData(
+ fileOrBlob,
+ function (data) {
+ console.log('Exif data: ', data.exif)
+ },
+ {
+ excludeExifTags: {
+ 0x8769: {
+ // ExifIFDPointer
+ 0x927c: true // MakerNote
+ },
+ 0x8825: true // GPSInfoIFDPointer
+ }
+ }
+)
+```
+
+### Exif writer
+
+The Exif parser extension also includes a minimal writer that allows to override
+the Exif `Orientation` value in the parsed `imageHead` `ArrayBuffer`:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ if (data.imageHead && data.exif) {
+ // Reset Exif Orientation data:
+ loadImage.writeExifData(data.imageHead, data, 'Orientation', 1)
+ img.toBlob(function (blob) {
+ loadImage.replaceHead(blob, data.imageHead, function (newBlob) {
+ // do something with newBlob
+ })
+ }, 'image/jpeg')
+ }
+ },
+ { meta: true, orientation: true, canvas: true, maxWidth: 800 }
+)
+```
+
+**Please note:**
+The Exif writer relies on the Exif tag offsets being available as
+`data.exifOffsets` property, which requires that Exif data has been parsed from
+the image.
+The Exif writer can only change existing values, not add new tags, e.g. it
+cannot add an Exif `Orientation` tag for an image that does not have one.
+
+### IPTC parser
+
+If you include the Load Image IPTC Parser extension, the argument passed to the
+callback for `parseMetaData` will contain the following additional properties if
+IPTC data could be found in the given image:
+
+- `iptc`: The parsed IPTC tags
+- `iptcOffsets`: The parsed IPTC tag offsets
+
+The `iptc` object stores the parsed IPTC tags:
+
+```js
+var objectname = data.iptc[5]
+```
+
+The `iptc` and `iptcOffsets` objects also provide a `get()` method to retrieve
+the tag value/offset via the tag's mapped name:
+
+```js
+var objectname = data.iptc.get('ObjectName')
+```
+
+By default, only the following names are mapped:
+
+- `ObjectName`
+
+If you also include the Load Image IPTC Map library, additional tag mappings
+become available, as well as three additional methods:
+
+- `iptc.getText()`
+- `iptc.getName()`
+- `iptc.getAll()`
+
+```js
+var keywords = data.iptc.getText('Keywords') // e.g.: ['Weather','Sky']
+
+var name = data.iptc.getName(5) // ObjectName
+
+// A map of all parsed tags with their mapped names/text as keys/values:
+var allTags = data.iptc.getAll()
+```
+
+#### IPTC parser options
+
+The IPTC parser adds additional options:
+
+- `disableIptc`: Disables IPTC parsing when true.
+- `disableIptcOffsets`: Disables storing IPTC tag offsets when `true`.
+- `includeIptcTags`: A map of IPTC tags to include for parsing (includes all but
+ the excluded tags by default).
+- `excludeIptcTags`: A map of IPTC tags to exclude from parsing (defaults to
+ exclude `ObjectPreviewData`).
+
+An example parsing only the `ObjectName` tag:
+
+```js
+loadImage.parseMetaData(
+ fileOrBlob,
+ function (data) {
+ console.log('IPTC data: ', data.iptc)
+ },
+ {
+ includeIptcTags: {
+ 5: true // ObjectName
+ }
+ }
+)
+```
+
+An example excluding `ApplicationRecordVersion` and `ObjectPreviewData`:
+
+```js
+loadImage.parseMetaData(
+ fileOrBlob,
+ function (data) {
+ console.log('IPTC data: ', data.iptc)
+ },
+ {
+ excludeIptcTags: {
+ 0: true, // ApplicationRecordVersion
+ 202: true // ObjectPreviewData
+ }
+ }
+)
+```
+
+## License
+
+The JavaScript Load Image library is released under the
+[MIT license](https://opensource.org/licenses/MIT).
+
+## Credits
+
+- Original image metadata handling implemented with the help and contribution of
+ Achim Stöhr.
+- Original Exif tags mapping based on Jacob Seidelin's
+ [exif-js](https://github.com/exif-js/exif-js) library.
+- Original IPTC parser implementation by
+ [Dave Bevan](https://github.com/bevand10).
diff --git a/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/index.js b/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/index.js
new file mode 100644
index 0000000000000..20875a2d08535
--- /dev/null
+++ b/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/index.js
@@ -0,0 +1,12 @@
+/* global module, require */
+
+module.exports = require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image')
+
+require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image-scale')
+require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image-meta')
+require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image-fetch')
+require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image-exif')
+require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image-exif-map')
+require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image-iptc')
+require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image-iptc-map')
+require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image-orientation')
diff --git a/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/load-image-exif-map.js b/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/load-image-exif-map.js
new file mode 100644
index 0000000000000..29f11aff226fc
--- /dev/null
+++ b/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/load-image-exif-map.js
@@ -0,0 +1,420 @@
+/*
+ * JavaScript Load Image Exif Map
+ * https://github.com/blueimp/JavaScript-Load-Image
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Exif tags mapping based on
+ * https://github.com/jseidelin/exif-js
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, module, require */
+
+;(function (factory) {
+ 'use strict'
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery/fileUploader/vendor/blueimp-load-image/js/load-image', 'jquery/fileUploader/vendor/blueimp-load-image/js/load-image-exif'], factory)
+ } else if (typeof module === 'object' && module.exports) {
+ factory(require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image'), require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image-exif'))
+ } else {
+ // Browser globals:
+ factory(window.loadImage)
+ }
+})(function (loadImage) {
+ 'use strict'
+
+ var ExifMapProto = loadImage.ExifMap.prototype
+
+ ExifMapProto.tags = {
+ // =================
+ // TIFF tags (IFD0):
+ // =================
+ 0x0100: 'ImageWidth',
+ 0x0101: 'ImageHeight',
+ 0x0102: 'BitsPerSample',
+ 0x0103: 'Compression',
+ 0x0106: 'PhotometricInterpretation',
+ 0x0112: 'Orientation',
+ 0x0115: 'SamplesPerPixel',
+ 0x011c: 'PlanarConfiguration',
+ 0x0212: 'YCbCrSubSampling',
+ 0x0213: 'YCbCrPositioning',
+ 0x011a: 'XResolution',
+ 0x011b: 'YResolution',
+ 0x0128: 'ResolutionUnit',
+ 0x0111: 'StripOffsets',
+ 0x0116: 'RowsPerStrip',
+ 0x0117: 'StripByteCounts',
+ 0x0201: 'JPEGInterchangeFormat',
+ 0x0202: 'JPEGInterchangeFormatLength',
+ 0x012d: 'TransferFunction',
+ 0x013e: 'WhitePoint',
+ 0x013f: 'PrimaryChromaticities',
+ 0x0211: 'YCbCrCoefficients',
+ 0x0214: 'ReferenceBlackWhite',
+ 0x0132: 'DateTime',
+ 0x010e: 'ImageDescription',
+ 0x010f: 'Make',
+ 0x0110: 'Model',
+ 0x0131: 'Software',
+ 0x013b: 'Artist',
+ 0x8298: 'Copyright',
+ 0x8769: {
+ // ExifIFDPointer
+ 0x9000: 'ExifVersion', // EXIF version
+ 0xa000: 'FlashpixVersion', // Flashpix format version
+ 0xa001: 'ColorSpace', // Color space information tag
+ 0xa002: 'PixelXDimension', // Valid width of meaningful image
+ 0xa003: 'PixelYDimension', // Valid height of meaningful image
+ 0xa500: 'Gamma',
+ 0x9101: 'ComponentsConfiguration', // Information about channels
+ 0x9102: 'CompressedBitsPerPixel', // Compressed bits per pixel
+ 0x927c: 'MakerNote', // Any desired information written by the manufacturer
+ 0x9286: 'UserComment', // Comments by user
+ 0xa004: 'RelatedSoundFile', // Name of related sound file
+ 0x9003: 'DateTimeOriginal', // Date and time when the original image was generated
+ 0x9004: 'DateTimeDigitized', // Date and time when the image was stored digitally
+ 0x9010: 'OffsetTime', // Time zone when the image file was last changed
+ 0x9011: 'OffsetTimeOriginal', // Time zone when the image was stored digitally
+ 0x9012: 'OffsetTimeDigitized', // Time zone when the image was stored digitally
+ 0x9290: 'SubSecTime', // Fractions of seconds for DateTime
+ 0x9291: 'SubSecTimeOriginal', // Fractions of seconds for DateTimeOriginal
+ 0x9292: 'SubSecTimeDigitized', // Fractions of seconds for DateTimeDigitized
+ 0x829a: 'ExposureTime', // Exposure time (in seconds)
+ 0x829d: 'FNumber',
+ 0x8822: 'ExposureProgram', // Exposure program
+ 0x8824: 'SpectralSensitivity', // Spectral sensitivity
+ 0x8827: 'PhotographicSensitivity', // EXIF 2.3, ISOSpeedRatings in EXIF 2.2
+ 0x8828: 'OECF', // Optoelectric conversion factor
+ 0x8830: 'SensitivityType',
+ 0x8831: 'StandardOutputSensitivity',
+ 0x8832: 'RecommendedExposureIndex',
+ 0x8833: 'ISOSpeed',
+ 0x8834: 'ISOSpeedLatitudeyyy',
+ 0x8835: 'ISOSpeedLatitudezzz',
+ 0x9201: 'ShutterSpeedValue', // Shutter speed
+ 0x9202: 'ApertureValue', // Lens aperture
+ 0x9203: 'BrightnessValue', // Value of brightness
+ 0x9204: 'ExposureBias', // Exposure bias
+ 0x9205: 'MaxApertureValue', // Smallest F number of lens
+ 0x9206: 'SubjectDistance', // Distance to subject in meters
+ 0x9207: 'MeteringMode', // Metering mode
+ 0x9208: 'LightSource', // Kind of light source
+ 0x9209: 'Flash', // Flash status
+ 0x9214: 'SubjectArea', // Location and area of main subject
+ 0x920a: 'FocalLength', // Focal length of the lens in mm
+ 0xa20b: 'FlashEnergy', // Strobe energy in BCPS
+ 0xa20c: 'SpatialFrequencyResponse',
+ 0xa20e: 'FocalPlaneXResolution', // Number of pixels in width direction per FPRUnit
+ 0xa20f: 'FocalPlaneYResolution', // Number of pixels in height direction per FPRUnit
+ 0xa210: 'FocalPlaneResolutionUnit', // Unit for measuring the focal plane resolution
+ 0xa214: 'SubjectLocation', // Location of subject in image
+ 0xa215: 'ExposureIndex', // Exposure index selected on camera
+ 0xa217: 'SensingMethod', // Image sensor type
+ 0xa300: 'FileSource', // Image source (3 == DSC)
+ 0xa301: 'SceneType', // Scene type (1 == directly photographed)
+ 0xa302: 'CFAPattern', // Color filter array geometric pattern
+ 0xa401: 'CustomRendered', // Special processing
+ 0xa402: 'ExposureMode', // Exposure mode
+ 0xa403: 'WhiteBalance', // 1 = auto white balance, 2 = manual
+ 0xa404: 'DigitalZoomRatio', // Digital zoom ratio
+ 0xa405: 'FocalLengthIn35mmFilm',
+ 0xa406: 'SceneCaptureType', // Type of scene
+ 0xa407: 'GainControl', // Degree of overall image gain adjustment
+ 0xa408: 'Contrast', // Direction of contrast processing applied by camera
+ 0xa409: 'Saturation', // Direction of saturation processing applied by camera
+ 0xa40a: 'Sharpness', // Direction of sharpness processing applied by camera
+ 0xa40b: 'DeviceSettingDescription',
+ 0xa40c: 'SubjectDistanceRange', // Distance to subject
+ 0xa420: 'ImageUniqueID', // Identifier assigned uniquely to each image
+ 0xa430: 'CameraOwnerName',
+ 0xa431: 'BodySerialNumber',
+ 0xa432: 'LensSpecification',
+ 0xa433: 'LensMake',
+ 0xa434: 'LensModel',
+ 0xa435: 'LensSerialNumber'
+ },
+ 0x8825: {
+ // GPSInfoIFDPointer
+ 0x0000: 'GPSVersionID',
+ 0x0001: 'GPSLatitudeRef',
+ 0x0002: 'GPSLatitude',
+ 0x0003: 'GPSLongitudeRef',
+ 0x0004: 'GPSLongitude',
+ 0x0005: 'GPSAltitudeRef',
+ 0x0006: 'GPSAltitude',
+ 0x0007: 'GPSTimeStamp',
+ 0x0008: 'GPSSatellites',
+ 0x0009: 'GPSStatus',
+ 0x000a: 'GPSMeasureMode',
+ 0x000b: 'GPSDOP',
+ 0x000c: 'GPSSpeedRef',
+ 0x000d: 'GPSSpeed',
+ 0x000e: 'GPSTrackRef',
+ 0x000f: 'GPSTrack',
+ 0x0010: 'GPSImgDirectionRef',
+ 0x0011: 'GPSImgDirection',
+ 0x0012: 'GPSMapDatum',
+ 0x0013: 'GPSDestLatitudeRef',
+ 0x0014: 'GPSDestLatitude',
+ 0x0015: 'GPSDestLongitudeRef',
+ 0x0016: 'GPSDestLongitude',
+ 0x0017: 'GPSDestBearingRef',
+ 0x0018: 'GPSDestBearing',
+ 0x0019: 'GPSDestDistanceRef',
+ 0x001a: 'GPSDestDistance',
+ 0x001b: 'GPSProcessingMethod',
+ 0x001c: 'GPSAreaInformation',
+ 0x001d: 'GPSDateStamp',
+ 0x001e: 'GPSDifferential',
+ 0x001f: 'GPSHPositioningError'
+ },
+ 0xa005: {
+ // InteroperabilityIFDPointer
+ 0x0001: 'InteroperabilityIndex'
+ }
+ }
+
+ // IFD1 directory can contain any IFD0 tags:
+ ExifMapProto.tags.ifd1 = ExifMapProto.tags
+
+ ExifMapProto.stringValues = {
+ ExposureProgram: {
+ 0: 'Undefined',
+ 1: 'Manual',
+ 2: 'Normal program',
+ 3: 'Aperture priority',
+ 4: 'Shutter priority',
+ 5: 'Creative program',
+ 6: 'Action program',
+ 7: 'Portrait mode',
+ 8: 'Landscape mode'
+ },
+ MeteringMode: {
+ 0: 'Unknown',
+ 1: 'Average',
+ 2: 'CenterWeightedAverage',
+ 3: 'Spot',
+ 4: 'MultiSpot',
+ 5: 'Pattern',
+ 6: 'Partial',
+ 255: 'Other'
+ },
+ LightSource: {
+ 0: 'Unknown',
+ 1: 'Daylight',
+ 2: 'Fluorescent',
+ 3: 'Tungsten (incandescent light)',
+ 4: 'Flash',
+ 9: 'Fine weather',
+ 10: 'Cloudy weather',
+ 11: 'Shade',
+ 12: 'Daylight fluorescent (D 5700 - 7100K)',
+ 13: 'Day white fluorescent (N 4600 - 5400K)',
+ 14: 'Cool white fluorescent (W 3900 - 4500K)',
+ 15: 'White fluorescent (WW 3200 - 3700K)',
+ 17: 'Standard light A',
+ 18: 'Standard light B',
+ 19: 'Standard light C',
+ 20: 'D55',
+ 21: 'D65',
+ 22: 'D75',
+ 23: 'D50',
+ 24: 'ISO studio tungsten',
+ 255: 'Other'
+ },
+ Flash: {
+ 0x0000: 'Flash did not fire',
+ 0x0001: 'Flash fired',
+ 0x0005: 'Strobe return light not detected',
+ 0x0007: 'Strobe return light detected',
+ 0x0009: 'Flash fired, compulsory flash mode',
+ 0x000d: 'Flash fired, compulsory flash mode, return light not detected',
+ 0x000f: 'Flash fired, compulsory flash mode, return light detected',
+ 0x0010: 'Flash did not fire, compulsory flash mode',
+ 0x0018: 'Flash did not fire, auto mode',
+ 0x0019: 'Flash fired, auto mode',
+ 0x001d: 'Flash fired, auto mode, return light not detected',
+ 0x001f: 'Flash fired, auto mode, return light detected',
+ 0x0020: 'No flash function',
+ 0x0041: 'Flash fired, red-eye reduction mode',
+ 0x0045: 'Flash fired, red-eye reduction mode, return light not detected',
+ 0x0047: 'Flash fired, red-eye reduction mode, return light detected',
+ 0x0049: 'Flash fired, compulsory flash mode, red-eye reduction mode',
+ 0x004d: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected',
+ 0x004f: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected',
+ 0x0059: 'Flash fired, auto mode, red-eye reduction mode',
+ 0x005d: 'Flash fired, auto mode, return light not detected, red-eye reduction mode',
+ 0x005f: 'Flash fired, auto mode, return light detected, red-eye reduction mode'
+ },
+ SensingMethod: {
+ 1: 'Undefined',
+ 2: 'One-chip color area sensor',
+ 3: 'Two-chip color area sensor',
+ 4: 'Three-chip color area sensor',
+ 5: 'Color sequential area sensor',
+ 7: 'Trilinear sensor',
+ 8: 'Color sequential linear sensor'
+ },
+ SceneCaptureType: {
+ 0: 'Standard',
+ 1: 'Landscape',
+ 2: 'Portrait',
+ 3: 'Night scene'
+ },
+ SceneType: {
+ 1: 'Directly photographed'
+ },
+ CustomRendered: {
+ 0: 'Normal process',
+ 1: 'Custom process'
+ },
+ WhiteBalance: {
+ 0: 'Auto white balance',
+ 1: 'Manual white balance'
+ },
+ GainControl: {
+ 0: 'None',
+ 1: 'Low gain up',
+ 2: 'High gain up',
+ 3: 'Low gain down',
+ 4: 'High gain down'
+ },
+ Contrast: {
+ 0: 'Normal',
+ 1: 'Soft',
+ 2: 'Hard'
+ },
+ Saturation: {
+ 0: 'Normal',
+ 1: 'Low saturation',
+ 2: 'High saturation'
+ },
+ Sharpness: {
+ 0: 'Normal',
+ 1: 'Soft',
+ 2: 'Hard'
+ },
+ SubjectDistanceRange: {
+ 0: 'Unknown',
+ 1: 'Macro',
+ 2: 'Close view',
+ 3: 'Distant view'
+ },
+ FileSource: {
+ 3: 'DSC'
+ },
+ ComponentsConfiguration: {
+ 0: '',
+ 1: 'Y',
+ 2: 'Cb',
+ 3: 'Cr',
+ 4: 'R',
+ 5: 'G',
+ 6: 'B'
+ },
+ Orientation: {
+ 1: 'Original',
+ 2: 'Horizontal flip',
+ 3: 'Rotate 180° CCW',
+ 4: 'Vertical flip',
+ 5: 'Vertical flip + Rotate 90° CW',
+ 6: 'Rotate 90° CW',
+ 7: 'Horizontal flip + Rotate 90° CW',
+ 8: 'Rotate 90° CCW'
+ }
+ }
+
+ ExifMapProto.getText = function (name) {
+ var value = this.get(name)
+ switch (name) {
+ case 'LightSource':
+ case 'Flash':
+ case 'MeteringMode':
+ case 'ExposureProgram':
+ case 'SensingMethod':
+ case 'SceneCaptureType':
+ case 'SceneType':
+ case 'CustomRendered':
+ case 'WhiteBalance':
+ case 'GainControl':
+ case 'Contrast':
+ case 'Saturation':
+ case 'Sharpness':
+ case 'SubjectDistanceRange':
+ case 'FileSource':
+ case 'Orientation':
+ return this.stringValues[name][value]
+ case 'ExifVersion':
+ case 'FlashpixVersion':
+ if (!value) return
+ return String.fromCharCode(value[0], value[1], value[2], value[3])
+ case 'ComponentsConfiguration':
+ if (!value) return
+ return (
+ this.stringValues[name][value[0]] +
+ this.stringValues[name][value[1]] +
+ this.stringValues[name][value[2]] +
+ this.stringValues[name][value[3]]
+ )
+ case 'GPSVersionID':
+ if (!value) return
+ return value[0] + '.' + value[1] + '.' + value[2] + '.' + value[3]
+ }
+ return String(value)
+ }
+
+ ExifMapProto.getAll = function () {
+ var map = {}
+ var prop
+ var obj
+ var name
+ for (prop in this) {
+ if (Object.prototype.hasOwnProperty.call(this, prop)) {
+ obj = this[prop]
+ if (obj && obj.getAll) {
+ map[this.ifds[prop].name] = obj.getAll()
+ } else {
+ name = this.tags[prop]
+ if (name) map[name] = this.getText(name)
+ }
+ }
+ }
+ return map
+ }
+
+ ExifMapProto.getName = function (tagCode) {
+ var name = this.tags[tagCode]
+ if (typeof name === 'object') return this.ifds[tagCode].name
+ return name
+ }
+
+ // Extend the map of tag names to tag codes:
+ ;(function () {
+ var tags = ExifMapProto.tags
+ var prop
+ var ifd
+ var subTags
+ // Map the tag names to tags:
+ for (prop in tags) {
+ if (Object.prototype.hasOwnProperty.call(tags, prop)) {
+ ifd = ExifMapProto.ifds[prop]
+ if (ifd) {
+ subTags = tags[prop]
+ for (prop in subTags) {
+ if (Object.prototype.hasOwnProperty.call(subTags, prop)) {
+ ifd.map[subTags[prop]] = Number(prop)
+ }
+ }
+ } else {
+ ExifMapProto.map[tags[prop]] = Number(prop)
+ }
+ }
+ }
+ })()
+})
diff --git a/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/load-image-exif.js b/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/load-image-exif.js
new file mode 100644
index 0000000000000..3c0937b8b590a
--- /dev/null
+++ b/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/load-image-exif.js
@@ -0,0 +1,460 @@
+/*
+ * JavaScript Load Image Exif Parser
+ * https://github.com/blueimp/JavaScript-Load-Image
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, module, require, DataView */
+
+/* eslint-disable no-console */
+
+;(function (factory) {
+ 'use strict'
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery/fileUploader/vendor/blueimp-load-image/js/load-image', 'jquery/fileUploader/vendor/blueimp-load-image/js/load-image-meta'], factory)
+ } else if (typeof module === 'object' && module.exports) {
+ factory(require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image'), require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image-meta'))
+ } else {
+ // Browser globals:
+ factory(window.loadImage)
+ }
+})(function (loadImage) {
+ 'use strict'
+
+ /**
+ * Exif tag map
+ *
+ * @name ExifMap
+ * @class
+ * @param {number|string} tagCode IFD tag code
+ */
+ function ExifMap(tagCode) {
+ if (tagCode) {
+ Object.defineProperty(this, 'map', {
+ value: this.ifds[tagCode].map
+ })
+ Object.defineProperty(this, 'tags', {
+ value: (this.tags && this.tags[tagCode]) || {}
+ })
+ }
+ }
+
+ ExifMap.prototype.map = {
+ Orientation: 0x0112,
+ Thumbnail: 'ifd1',
+ Blob: 0x0201, // Alias for JPEGInterchangeFormat
+ Exif: 0x8769,
+ GPSInfo: 0x8825,
+ Interoperability: 0xa005
+ }
+
+ ExifMap.prototype.ifds = {
+ ifd1: { name: 'Thumbnail', map: ExifMap.prototype.map },
+ 0x8769: { name: 'Exif', map: {} },
+ 0x8825: { name: 'GPSInfo', map: {} },
+ 0xa005: { name: 'Interoperability', map: {} }
+ }
+
+ /**
+ * Retrieves exif tag value
+ *
+ * @param {number|string} id Exif tag code or name
+ * @returns {object} Exif tag value
+ */
+ ExifMap.prototype.get = function (id) {
+ return this[id] || this[this.map[id]]
+ }
+
+ /**
+ * Returns the Exif Thumbnail data as Blob.
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} offset Thumbnail data offset
+ * @param {number} length Thumbnail data length
+ * @returns {undefined|Blob} Returns the Thumbnail Blob or undefined
+ */
+ function getExifThumbnail(dataView, offset, length) {
+ if (!length) return
+ if (offset + length > dataView.byteLength) {
+ console.log('Invalid Exif data: Invalid thumbnail data.')
+ return
+ }
+ return new Blob(
+ [loadImage.bufferSlice.call(dataView.buffer, offset, offset + length)],
+ {
+ type: 'image/jpeg'
+ }
+ )
+ }
+
+ var ExifTagTypes = {
+ // byte, 8-bit unsigned int:
+ 1: {
+ getValue: function (dataView, dataOffset) {
+ return dataView.getUint8(dataOffset)
+ },
+ size: 1
+ },
+ // ascii, 8-bit byte:
+ 2: {
+ getValue: function (dataView, dataOffset) {
+ return String.fromCharCode(dataView.getUint8(dataOffset))
+ },
+ size: 1,
+ ascii: true
+ },
+ // short, 16 bit int:
+ 3: {
+ getValue: function (dataView, dataOffset, littleEndian) {
+ return dataView.getUint16(dataOffset, littleEndian)
+ },
+ size: 2
+ },
+ // long, 32 bit int:
+ 4: {
+ getValue: function (dataView, dataOffset, littleEndian) {
+ return dataView.getUint32(dataOffset, littleEndian)
+ },
+ size: 4
+ },
+ // rational = two long values, first is numerator, second is denominator:
+ 5: {
+ getValue: function (dataView, dataOffset, littleEndian) {
+ return (
+ dataView.getUint32(dataOffset, littleEndian) /
+ dataView.getUint32(dataOffset + 4, littleEndian)
+ )
+ },
+ size: 8
+ },
+ // slong, 32 bit signed int:
+ 9: {
+ getValue: function (dataView, dataOffset, littleEndian) {
+ return dataView.getInt32(dataOffset, littleEndian)
+ },
+ size: 4
+ },
+ // srational, two slongs, first is numerator, second is denominator:
+ 10: {
+ getValue: function (dataView, dataOffset, littleEndian) {
+ return (
+ dataView.getInt32(dataOffset, littleEndian) /
+ dataView.getInt32(dataOffset + 4, littleEndian)
+ )
+ },
+ size: 8
+ }
+ }
+ // undefined, 8-bit byte, value depending on field:
+ ExifTagTypes[7] = ExifTagTypes[1]
+
+ /**
+ * Returns Exif tag value.
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} tiffOffset TIFF offset
+ * @param {number} offset Tag offset
+ * @param {number} type Tag type
+ * @param {number} length Tag length
+ * @param {boolean} littleEndian Little endian encoding
+ * @returns {object} Tag value
+ */
+ function getExifValue(
+ dataView,
+ tiffOffset,
+ offset,
+ type,
+ length,
+ littleEndian
+ ) {
+ var tagType = ExifTagTypes[type]
+ var tagSize
+ var dataOffset
+ var values
+ var i
+ var str
+ var c
+ if (!tagType) {
+ console.log('Invalid Exif data: Invalid tag type.')
+ return
+ }
+ tagSize = tagType.size * length
+ // Determine if the value is contained in the dataOffset bytes,
+ // or if the value at the dataOffset is a pointer to the actual data:
+ dataOffset =
+ tagSize > 4
+ ? tiffOffset + dataView.getUint32(offset + 8, littleEndian)
+ : offset + 8
+ if (dataOffset + tagSize > dataView.byteLength) {
+ console.log('Invalid Exif data: Invalid data offset.')
+ return
+ }
+ if (length === 1) {
+ return tagType.getValue(dataView, dataOffset, littleEndian)
+ }
+ values = []
+ for (i = 0; i < length; i += 1) {
+ values[i] = tagType.getValue(
+ dataView,
+ dataOffset + i * tagType.size,
+ littleEndian
+ )
+ }
+ if (tagType.ascii) {
+ str = ''
+ // Concatenate the chars:
+ for (i = 0; i < values.length; i += 1) {
+ c = values[i]
+ // Ignore the terminating NULL byte(s):
+ if (c === '\u0000') {
+ break
+ }
+ str += c
+ }
+ return str
+ }
+ return values
+ }
+
+ /**
+ * Determines if the given tag should be included.
+ *
+ * @param {object} includeTags Map of tags to include
+ * @param {object} excludeTags Map of tags to exclude
+ * @param {number|string} tagCode Tag code to check
+ * @returns {boolean} True if the tag should be included
+ */
+ function shouldIncludeTag(includeTags, excludeTags, tagCode) {
+ return (
+ (!includeTags || includeTags[tagCode]) &&
+ (!excludeTags || excludeTags[tagCode] !== true)
+ )
+ }
+
+ /**
+ * Parses Exif tags.
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} tiffOffset TIFF offset
+ * @param {number} dirOffset Directory offset
+ * @param {boolean} littleEndian Little endian encoding
+ * @param {ExifMap} tags Map to store parsed exif tags
+ * @param {ExifMap} tagOffsets Map to store parsed exif tag offsets
+ * @param {object} includeTags Map of tags to include
+ * @param {object} excludeTags Map of tags to exclude
+ * @returns {number} Next directory offset
+ */
+ function parseExifTags(
+ dataView,
+ tiffOffset,
+ dirOffset,
+ littleEndian,
+ tags,
+ tagOffsets,
+ includeTags,
+ excludeTags
+ ) {
+ var tagsNumber, dirEndOffset, i, tagOffset, tagNumber, tagValue
+ if (dirOffset + 6 > dataView.byteLength) {
+ console.log('Invalid Exif data: Invalid directory offset.')
+ return
+ }
+ tagsNumber = dataView.getUint16(dirOffset, littleEndian)
+ dirEndOffset = dirOffset + 2 + 12 * tagsNumber
+ if (dirEndOffset + 4 > dataView.byteLength) {
+ console.log('Invalid Exif data: Invalid directory size.')
+ return
+ }
+ for (i = 0; i < tagsNumber; i += 1) {
+ tagOffset = dirOffset + 2 + 12 * i
+ tagNumber = dataView.getUint16(tagOffset, littleEndian)
+ if (!shouldIncludeTag(includeTags, excludeTags, tagNumber)) continue
+ tagValue = getExifValue(
+ dataView,
+ tiffOffset,
+ tagOffset,
+ dataView.getUint16(tagOffset + 2, littleEndian), // tag type
+ dataView.getUint32(tagOffset + 4, littleEndian), // tag length
+ littleEndian
+ )
+ tags[tagNumber] = tagValue
+ if (tagOffsets) {
+ tagOffsets[tagNumber] = tagOffset
+ }
+ }
+ // Return the offset to the next directory:
+ return dataView.getUint32(dirEndOffset, littleEndian)
+ }
+
+ /**
+ * Parses tags in a given IFD (Image File Directory).
+ *
+ * @param {object} data Data object to store exif tags and offsets
+ * @param {number|string} tagCode IFD tag code
+ * @param {DataView} dataView Data view interface
+ * @param {number} tiffOffset TIFF offset
+ * @param {boolean} littleEndian Little endian encoding
+ * @param {object} includeTags Map of tags to include
+ * @param {object} excludeTags Map of tags to exclude
+ */
+ function parseExifIFD(
+ data,
+ tagCode,
+ dataView,
+ tiffOffset,
+ littleEndian,
+ includeTags,
+ excludeTags
+ ) {
+ var dirOffset = data.exif[tagCode]
+ if (dirOffset) {
+ data.exif[tagCode] = new ExifMap(tagCode)
+ if (data.exifOffsets) {
+ data.exifOffsets[tagCode] = new ExifMap(tagCode)
+ }
+ parseExifTags(
+ dataView,
+ tiffOffset,
+ tiffOffset + dirOffset,
+ littleEndian,
+ data.exif[tagCode],
+ data.exifOffsets && data.exifOffsets[tagCode],
+ includeTags && includeTags[tagCode],
+ excludeTags && excludeTags[tagCode]
+ )
+ }
+ }
+
+ loadImage.parseExifData = function (dataView, offset, length, data, options) {
+ if (options.disableExif) {
+ return
+ }
+ var includeTags = options.includeExifTags
+ var excludeTags = options.excludeExifTags || {
+ 0x8769: {
+ // ExifIFDPointer
+ 0x927c: true // MakerNote
+ }
+ }
+ var tiffOffset = offset + 10
+ var littleEndian
+ var dirOffset
+ var thumbnailIFD
+ // Check for the ASCII code for "Exif" (0x45786966):
+ if (dataView.getUint32(offset + 4) !== 0x45786966) {
+ // No Exif data, might be XMP data instead
+ return
+ }
+ if (tiffOffset + 8 > dataView.byteLength) {
+ console.log('Invalid Exif data: Invalid segment size.')
+ return
+ }
+ // Check for the two null bytes:
+ if (dataView.getUint16(offset + 8) !== 0x0000) {
+ console.log('Invalid Exif data: Missing byte alignment offset.')
+ return
+ }
+ // Check the byte alignment:
+ switch (dataView.getUint16(tiffOffset)) {
+ case 0x4949:
+ littleEndian = true
+ break
+ case 0x4d4d:
+ littleEndian = false
+ break
+ default:
+ console.log('Invalid Exif data: Invalid byte alignment marker.')
+ return
+ }
+ // Check for the TIFF tag marker (0x002A):
+ if (dataView.getUint16(tiffOffset + 2, littleEndian) !== 0x002a) {
+ console.log('Invalid Exif data: Missing TIFF marker.')
+ return
+ }
+ // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
+ dirOffset = dataView.getUint32(tiffOffset + 4, littleEndian)
+ // Create the exif object to store the tags:
+ data.exif = new ExifMap()
+ if (!options.disableExifOffsets) {
+ data.exifOffsets = new ExifMap()
+ data.exifTiffOffset = tiffOffset
+ data.exifLittleEndian = littleEndian
+ }
+ // Parse the tags of the main image directory (IFD0) and retrieve the
+ // offset to the next directory (IFD1), usually the thumbnail directory:
+ dirOffset = parseExifTags(
+ dataView,
+ tiffOffset,
+ tiffOffset + dirOffset,
+ littleEndian,
+ data.exif,
+ data.exifOffsets,
+ includeTags,
+ excludeTags
+ )
+ if (dirOffset && shouldIncludeTag(includeTags, excludeTags, 'ifd1')) {
+ data.exif.ifd1 = dirOffset
+ if (data.exifOffsets) {
+ data.exifOffsets.ifd1 = tiffOffset + dirOffset
+ }
+ }
+ Object.keys(data.exif.ifds).forEach(function (tagCode) {
+ parseExifIFD(
+ data,
+ tagCode,
+ dataView,
+ tiffOffset,
+ littleEndian,
+ includeTags,
+ excludeTags
+ )
+ })
+ thumbnailIFD = data.exif.ifd1
+ // Check for JPEG Thumbnail offset and data length:
+ if (thumbnailIFD && thumbnailIFD[0x0201]) {
+ thumbnailIFD[0x0201] = getExifThumbnail(
+ dataView,
+ tiffOffset + thumbnailIFD[0x0201],
+ thumbnailIFD[0x0202] // Thumbnail data length
+ )
+ }
+ }
+
+ // Registers the Exif parser for the APP1 JPEG metadata segment:
+ loadImage.metaDataParsers.jpeg[0xffe1].push(loadImage.parseExifData)
+
+ loadImage.exifWriters = {
+ // Orientation writer:
+ 0x0112: function (buffer, data, value) {
+ var orientationOffset = data.exifOffsets[0x0112]
+ if (!orientationOffset) return buffer
+ var view = new DataView(buffer, orientationOffset + 8, 2)
+ view.setUint16(0, value, data.exifLittleEndian)
+ return buffer
+ }
+ }
+
+ loadImage.writeExifData = function (buffer, data, id, value) {
+ loadImage.exifWriters[data.exif.map[id]](buffer, data, value)
+ }
+
+ loadImage.ExifMap = ExifMap
+
+ // Adds the following properties to the parseMetaData callback data:
+ // - exif: The parsed Exif tags
+ // - exifOffsets: The parsed Exif tag offsets
+ // - exifTiffOffset: TIFF header offset (used for offset pointers)
+ // - exifLittleEndian: little endian order if true, big endian if false
+
+ // Adds the following options to the parseMetaData method:
+ // - disableExif: Disables Exif parsing when true.
+ // - disableExifOffsets: Disables storing Exif tag offsets when true.
+ // - includeExifTags: A map of Exif tags to include for parsing.
+ // - excludeExifTags: A map of Exif tags to exclude from parsing.
+})
diff --git a/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/load-image-fetch.js b/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/load-image-fetch.js
new file mode 100644
index 0000000000000..28a28fb83e6cd
--- /dev/null
+++ b/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/load-image-fetch.js
@@ -0,0 +1,103 @@
+/*
+ * JavaScript Load Image Fetch
+ * https://github.com/blueimp/JavaScript-Load-Image
+ *
+ * Copyright 2017, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, module, require, Promise */
+
+;(function (factory) {
+ 'use strict'
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery/fileUploader/vendor/blueimp-load-image/js/load-image'], factory)
+ } else if (typeof module === 'object' && module.exports) {
+ factory(require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image'))
+ } else {
+ // Browser globals:
+ factory(window.loadImage)
+ }
+})(function (loadImage) {
+ 'use strict'
+
+ var global = loadImage.global
+
+ if (
+ global.fetch &&
+ global.Request &&
+ global.Response &&
+ global.Response.prototype.blob
+ ) {
+ loadImage.fetchBlob = function (url, callback, options) {
+ /**
+ * Fetch response handler.
+ *
+ * @param {Response} response Fetch response
+ * @returns {Blob} Fetched Blob.
+ */
+ function responseHandler(response) {
+ return response.blob()
+ }
+ if (global.Promise && typeof callback !== 'function') {
+ return fetch(new Request(url, callback)).then(responseHandler)
+ }
+ fetch(new Request(url, options))
+ .then(responseHandler)
+ .then(callback)
+ [
+ // Avoid parsing error in IE<9, where catch is a reserved word.
+ // eslint-disable-next-line dot-notation
+ 'catch'
+ ](function (err) {
+ callback(null, err)
+ })
+ }
+ } else if (
+ global.XMLHttpRequest &&
+ // https://xhr.spec.whatwg.org/#the-responsetype-attribute
+ new XMLHttpRequest().responseType === ''
+ ) {
+ loadImage.fetchBlob = function (url, callback, options) {
+ /**
+ * Promise executor
+ *
+ * @param {Function} resolve Resolution function
+ * @param {Function} reject Rejection function
+ */
+ function executor(resolve, reject) {
+ options = options || {} // eslint-disable-line no-param-reassign
+ var req = new XMLHttpRequest()
+ req.open(options.method || 'GET', url)
+ if (options.headers) {
+ Object.keys(options.headers).forEach(function (key) {
+ req.setRequestHeader(key, options.headers[key])
+ })
+ }
+ req.withCredentials = options.credentials === 'include'
+ req.responseType = 'blob'
+ req.onload = function () {
+ resolve(req.response)
+ }
+ req.onerror = req.onabort = req.ontimeout = function (err) {
+ if (resolve === reject) {
+ // Not using Promises
+ reject(null, err)
+ } else {
+ reject(err)
+ }
+ }
+ req.send(options.body)
+ }
+ if (global.Promise && typeof callback !== 'function') {
+ options = callback // eslint-disable-line no-param-reassign
+ return new Promise(executor)
+ }
+ return executor(callback, callback)
+ }
+ }
+})
diff --git a/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/load-image-iptc-map.js b/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/load-image-iptc-map.js
new file mode 100644
index 0000000000000..cd959a24b3541
--- /dev/null
+++ b/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/load-image-iptc-map.js
@@ -0,0 +1,169 @@
+/*
+ * JavaScript Load Image IPTC Map
+ * https://github.com/blueimp/JavaScript-Load-Image
+ *
+ * Copyright 2013, Sebastian Tschan
+ * Copyright 2018, Dave Bevan
+ *
+ * IPTC tags mapping based on
+ * https://iptc.org/standards/photo-metadata
+ * https://exiftool.org/TagNames/IPTC.html
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, module, require */
+
+;(function (factory) {
+ 'use strict'
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery/fileUploader/vendor/blueimp-load-image/js/load-image', 'jquery/fileUploader/vendor/blueimp-load-image/js/load-image-iptc'], factory)
+ } else if (typeof module === 'object' && module.exports) {
+ factory(require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image'), require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image-iptc'))
+ } else {
+ // Browser globals:
+ factory(window.loadImage)
+ }
+})(function (loadImage) {
+ 'use strict'
+
+ var IptcMapProto = loadImage.IptcMap.prototype
+
+ IptcMapProto.tags = {
+ 0: 'ApplicationRecordVersion',
+ 3: 'ObjectTypeReference',
+ 4: 'ObjectAttributeReference',
+ 5: 'ObjectName',
+ 7: 'EditStatus',
+ 8: 'EditorialUpdate',
+ 10: 'Urgency',
+ 12: 'SubjectReference',
+ 15: 'Category',
+ 20: 'SupplementalCategories',
+ 22: 'FixtureIdentifier',
+ 25: 'Keywords',
+ 26: 'ContentLocationCode',
+ 27: 'ContentLocationName',
+ 30: 'ReleaseDate',
+ 35: 'ReleaseTime',
+ 37: 'ExpirationDate',
+ 38: 'ExpirationTime',
+ 40: 'SpecialInstructions',
+ 42: 'ActionAdvised',
+ 45: 'ReferenceService',
+ 47: 'ReferenceDate',
+ 50: 'ReferenceNumber',
+ 55: 'DateCreated',
+ 60: 'TimeCreated',
+ 62: 'DigitalCreationDate',
+ 63: 'DigitalCreationTime',
+ 65: 'OriginatingProgram',
+ 70: 'ProgramVersion',
+ 75: 'ObjectCycle',
+ 80: 'Byline',
+ 85: 'BylineTitle',
+ 90: 'City',
+ 92: 'Sublocation',
+ 95: 'State',
+ 100: 'CountryCode',
+ 101: 'Country',
+ 103: 'OriginalTransmissionReference',
+ 105: 'Headline',
+ 110: 'Credit',
+ 115: 'Source',
+ 116: 'CopyrightNotice',
+ 118: 'Contact',
+ 120: 'Caption',
+ 121: 'LocalCaption',
+ 122: 'Writer',
+ 125: 'RasterizedCaption',
+ 130: 'ImageType',
+ 131: 'ImageOrientation',
+ 135: 'LanguageIdentifier',
+ 150: 'AudioType',
+ 151: 'AudioSamplingRate',
+ 152: 'AudioSamplingResolution',
+ 153: 'AudioDuration',
+ 154: 'AudioOutcue',
+ 184: 'JobID',
+ 185: 'MasterDocumentID',
+ 186: 'ShortDocumentID',
+ 187: 'UniqueDocumentID',
+ 188: 'OwnerID',
+ 200: 'ObjectPreviewFileFormat',
+ 201: 'ObjectPreviewFileVersion',
+ 202: 'ObjectPreviewData',
+ 221: 'Prefs',
+ 225: 'ClassifyState',
+ 228: 'SimilarityIndex',
+ 230: 'DocumentNotes',
+ 231: 'DocumentHistory',
+ 232: 'ExifCameraInfo',
+ 255: 'CatalogSets'
+ }
+
+ IptcMapProto.stringValues = {
+ 10: {
+ 0: '0 (reserved)',
+ 1: '1 (most urgent)',
+ 2: '2',
+ 3: '3',
+ 4: '4',
+ 5: '5 (normal urgency)',
+ 6: '6',
+ 7: '7',
+ 8: '8 (least urgent)',
+ 9: '9 (user-defined priority)'
+ },
+ 75: {
+ a: 'Morning',
+ b: 'Both Morning and Evening',
+ p: 'Evening'
+ },
+ 131: {
+ L: 'Landscape',
+ P: 'Portrait',
+ S: 'Square'
+ }
+ }
+
+ IptcMapProto.getText = function (id) {
+ var value = this.get(id)
+ var tagCode = this.map[id]
+ var stringValue = this.stringValues[tagCode]
+ if (stringValue) return stringValue[value]
+ return String(value)
+ }
+
+ IptcMapProto.getAll = function () {
+ var map = {}
+ var prop
+ var name
+ for (prop in this) {
+ if (Object.prototype.hasOwnProperty.call(this, prop)) {
+ name = this.tags[prop]
+ if (name) map[name] = this.getText(name)
+ }
+ }
+ return map
+ }
+
+ IptcMapProto.getName = function (tagCode) {
+ return this.tags[tagCode]
+ }
+
+ // Extend the map of tag names to tag codes:
+ ;(function () {
+ var tags = IptcMapProto.tags
+ var map = IptcMapProto.map || {}
+ var prop
+ // Map the tag names to tags:
+ for (prop in tags) {
+ if (Object.prototype.hasOwnProperty.call(tags, prop)) {
+ map[tags[prop]] = Number(prop)
+ }
+ }
+ })()
+})
diff --git a/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/load-image-iptc.js b/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/load-image-iptc.js
new file mode 100644
index 0000000000000..f6b4594f9e130
--- /dev/null
+++ b/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/load-image-iptc.js
@@ -0,0 +1,239 @@
+/*
+ * JavaScript Load Image IPTC Parser
+ * https://github.com/blueimp/JavaScript-Load-Image
+ *
+ * Copyright 2013, Sebastian Tschan
+ * Copyright 2018, Dave Bevan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, module, require, DataView */
+
+;(function (factory) {
+ 'use strict'
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery/fileUploader/vendor/blueimp-load-image/js/load-image', 'jquery/fileUploader/vendor/blueimp-load-image/js/load-image-meta'], factory)
+ } else if (typeof module === 'object' && module.exports) {
+ factory(require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image'), require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image-meta'))
+ } else {
+ // Browser globals:
+ factory(window.loadImage)
+ }
+})(function (loadImage) {
+ 'use strict'
+
+ /**
+ * IPTC tag map
+ *
+ * @name IptcMap
+ * @class
+ */
+ function IptcMap() {}
+
+ IptcMap.prototype.map = {
+ ObjectName: 5
+ }
+
+ IptcMap.prototype.types = {
+ 0: 'Uint16', // ApplicationRecordVersion
+ 200: 'Uint16', // ObjectPreviewFileFormat
+ 201: 'Uint16', // ObjectPreviewFileVersion
+ 202: 'binary' // ObjectPreviewData
+ }
+
+ /**
+ * Retrieves IPTC tag value
+ *
+ * @param {number|string} id IPTC tag code or name
+ * @returns {object} IPTC tag value
+ */
+ IptcMap.prototype.get = function (id) {
+ return this[id] || this[this.map[id]]
+ }
+
+ /**
+ * Retrieves string for the given DataView and range
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} offset Offset start
+ * @param {number} length Offset length
+ * @returns {string} String value
+ */
+ function getStringValue(dataView, offset, length) {
+ var outstr = ''
+ var end = offset + length
+ for (var n = offset; n < end; n += 1) {
+ outstr += String.fromCharCode(dataView.getUint8(n))
+ }
+ return outstr
+ }
+
+ /**
+ * Retrieves tag value for the given DataView and range
+ *
+ * @param {number} tagCode tag code
+ * @param {IptcMap} map IPTC tag map
+ * @param {DataView} dataView Data view interface
+ * @param {number} offset Range start
+ * @param {number} length Range length
+ * @returns {object} Tag value
+ */
+ function getTagValue(tagCode, map, dataView, offset, length) {
+ if (map.types[tagCode] === 'binary') {
+ return new Blob([dataView.buffer.slice(offset, offset + length)])
+ }
+ if (map.types[tagCode] === 'Uint16') {
+ return dataView.getUint16(offset)
+ }
+ return getStringValue(dataView, offset, length)
+ }
+
+ /**
+ * Combines IPTC value with existing ones.
+ *
+ * @param {object} value Existing IPTC field value
+ * @param {object} newValue New IPTC field value
+ * @returns {object} Resulting IPTC field value
+ */
+ function combineTagValues(value, newValue) {
+ if (value === undefined) return newValue
+ if (value instanceof Array) {
+ value.push(newValue)
+ return value
+ }
+ return [value, newValue]
+ }
+
+ /**
+ * Parses IPTC tags.
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} segmentOffset Segment offset
+ * @param {number} segmentLength Segment length
+ * @param {object} data Data export object
+ * @param {object} includeTags Map of tags to include
+ * @param {object} excludeTags Map of tags to exclude
+ */
+ function parseIptcTags(
+ dataView,
+ segmentOffset,
+ segmentLength,
+ data,
+ includeTags,
+ excludeTags
+ ) {
+ var value, tagSize, tagCode
+ var segmentEnd = segmentOffset + segmentLength
+ var offset = segmentOffset
+ while (offset < segmentEnd) {
+ if (
+ dataView.getUint8(offset) === 0x1c && // tag marker
+ dataView.getUint8(offset + 1) === 0x02 // record number, only handles v2
+ ) {
+ tagCode = dataView.getUint8(offset + 2)
+ if (
+ (!includeTags || includeTags[tagCode]) &&
+ (!excludeTags || !excludeTags[tagCode])
+ ) {
+ tagSize = dataView.getInt16(offset + 3)
+ value = getTagValue(tagCode, data.iptc, dataView, offset + 5, tagSize)
+ data.iptc[tagCode] = combineTagValues(data.iptc[tagCode], value)
+ if (data.iptcOffsets) {
+ data.iptcOffsets[tagCode] = offset
+ }
+ }
+ }
+ offset += 1
+ }
+ }
+
+ /**
+ * Tests if field segment starts at offset.
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} offset Segment offset
+ * @returns {boolean} True if '8BIM' exists at offset
+ */
+ function isSegmentStart(dataView, offset) {
+ return (
+ dataView.getUint32(offset) === 0x3842494d && // Photoshop segment start
+ dataView.getUint16(offset + 4) === 0x0404 // IPTC segment start
+ )
+ }
+
+ /**
+ * Returns header length.
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} offset Segment offset
+ * @returns {number} Header length
+ */
+ function getHeaderLength(dataView, offset) {
+ var length = dataView.getUint8(offset + 7)
+ if (length % 2 !== 0) length += 1
+ // Check for pre photoshop 6 format
+ if (length === 0) {
+ // Always 4
+ length = 4
+ }
+ return length
+ }
+
+ loadImage.parseIptcData = function (dataView, offset, length, data, options) {
+ if (options.disableIptc) {
+ return
+ }
+ var markerLength = offset + length
+ while (offset + 8 < markerLength) {
+ if (isSegmentStart(dataView, offset)) {
+ var headerLength = getHeaderLength(dataView, offset)
+ var segmentOffset = offset + 8 + headerLength
+ if (segmentOffset > markerLength) {
+ // eslint-disable-next-line no-console
+ console.log('Invalid IPTC data: Invalid segment offset.')
+ break
+ }
+ var segmentLength = dataView.getUint16(offset + 6 + headerLength)
+ if (offset + segmentLength > markerLength) {
+ // eslint-disable-next-line no-console
+ console.log('Invalid IPTC data: Invalid segment size.')
+ break
+ }
+ // Create the iptc object to store the tags:
+ data.iptc = new IptcMap()
+ if (!options.disableIptcOffsets) {
+ data.iptcOffsets = new IptcMap()
+ }
+ parseIptcTags(
+ dataView,
+ segmentOffset,
+ segmentLength,
+ data,
+ options.includeIptcTags,
+ options.excludeIptcTags || { 202: true } // ObjectPreviewData
+ )
+ return
+ }
+ // eslint-disable-next-line no-param-reassign
+ offset += 1
+ }
+ }
+
+ // Registers this IPTC parser for the APP13 JPEG metadata segment:
+ loadImage.metaDataParsers.jpeg[0xffed].push(loadImage.parseIptcData)
+
+ loadImage.IptcMap = IptcMap
+
+ // Adds the following properties to the parseMetaData callback data:
+ // - iptc: The iptc tags, parsed by the parseIptcData method
+
+ // Adds the following options to the parseMetaData method:
+ // - disableIptc: Disables IPTC parsing when true.
+ // - disableIptcOffsets: Disables storing IPTC tag offsets when true.
+ // - includeIptcTags: A map of IPTC tags to include for parsing.
+ // - excludeIptcTags: A map of IPTC tags to exclude from parsing.
+})
diff --git a/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/load-image-meta.js b/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/load-image-meta.js
new file mode 100644
index 0000000000000..20a06184c640d
--- /dev/null
+++ b/lib/web/jquery/fileUploader/vendor/blueimp-load-image/js/load-image-meta.js
@@ -0,0 +1,259 @@
+/*
+ * JavaScript Load Image Meta
+ * https://github.com/blueimp/JavaScript-Load-Image
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Image metadata handling implementation
+ * based on the help and contribution of
+ * Achim Stöhr.
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, module, require, Promise, DataView, Uint8Array, ArrayBuffer */
+
+;(function (factory) {
+ 'use strict'
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery/fileUploader/vendor/blueimp-load-image/js/load-image'], factory)
+ } else if (typeof module === 'object' && module.exports) {
+ factory(require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image'))
+ } else {
+ // Browser globals:
+ factory(window.loadImage)
+ }
+})(function (loadImage) {
+ 'use strict'
+
+ var global = loadImage.global
+ var originalTransform = loadImage.transform
+
+ var blobSlice =
+ global.Blob &&
+ (Blob.prototype.slice ||
+ Blob.prototype.webkitSlice ||
+ Blob.prototype.mozSlice)
+
+ var bufferSlice =
+ (global.ArrayBuffer && ArrayBuffer.prototype.slice) ||
+ function (begin, end) {
+ // Polyfill for IE10, which does not support ArrayBuffer.slice
+ // eslint-disable-next-line no-param-reassign
+ end = end || this.byteLength - begin
+ var arr1 = new Uint8Array(this, begin, end)
+ var arr2 = new Uint8Array(end)
+ arr2.set(arr1)
+ return arr2.buffer
+ }
+
+ var metaDataParsers = {
+ jpeg: {
+ 0xffe1: [], // APP1 marker
+ 0xffed: [] // APP13 marker
+ }
+ }
+
+ /**
+ * Parses image metadata and calls the callback with an object argument
+ * with the following property:
+ * - imageHead: The complete image head as ArrayBuffer
+ * The options argument accepts an object and supports the following
+ * properties:
+ * - maxMetaDataSize: Defines the maximum number of bytes to parse.
+ * - disableImageHead: Disables creating the imageHead property.
+ *
+ * @param {Blob} file Blob object
+ * @param {Function} [callback] Callback function
+ * @param {object} [options] Parsing options
+ * @param {object} [data] Result data object
+ * @returns {Promise |