diff --git a/ajax/libs/file-uploader/3.2.0/fineuploader-jquery.js b/ajax/libs/file-uploader/3.2.0/fineuploader-jquery.js
new file mode 100644
index 000000000..8fe3edd0d
--- /dev/null
+++ b/ajax/libs/file-uploader/3.2.0/fineuploader-jquery.js
@@ -0,0 +1,3209 @@
+/**
+ * http://github.com/Valums-File-Uploader/file-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Original version: 1.0 © 2010 Andrew Valums ( andrew(at)valums.com )
+ * Current Maintainer (2.0+): © 2012, Ray Nicholus ( fineuploader(at)garstasio.com )
+ *
+ * Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity !== null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+ if (window.File && maybeFileOrInput instanceof File) {
+ return true;
+ }
+ else if (window.HTMLInputElement) {
+ if (maybeFileOrInput instanceof HTMLInputElement) {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ else if (maybeFileOrInput.tagName) {
+ if (maybeFileOrInput.tagName.toLowerCase() === 'input') {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice || File.prototype.webkitSlice || File.prototype.mozSlice);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(obj, callback) {
+ "use strict";
+ var key, retVal;
+ if (obj) {
+ for (key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ retVal = callback(key, obj[key]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(encodeURIComponent(key), encodeURIComponent(val()));
+ }
+ else {
+ formData.append(encodeURIComponent(key), encodeURIComponent(val));
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ c;
+
+ for(var i=0;i < ca.length;i++) {
+ c = ca[i];
+ while (c.charAt(0)==' ') {
+ c = c.substring(1,c.length);
+ }
+ if (c.indexOf(nameEQ) === 0) {
+ return c.substring(nameEQ.length,c.length);
+ }
+ }
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = cookie.trim();
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (typeof JSON.parse === "function") {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+qq.UploadButton = function(o){
+ this._options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input){},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ qq.extend(this._options, o);
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._element = this._options.element;
+
+ // make button suitable container for input
+ qq(this._element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ this._input = this._createInput();
+};
+
+qq.UploadButton.prototype = {
+ /* returns file input element */
+ getInput: function(){
+ return this._input;
+ },
+ /* cleans/recreates the file input */
+ reset: function(){
+ if (this._input.parentNode){
+ qq(this._input).remove();
+ }
+
+ qq(this._element).removeClass(this._options.focusClass);
+ this._input = this._createInput();
+ },
+ _createInput: function(){
+ var input = document.createElement("input");
+
+ if (this._options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (this._options.acceptFiles) input.setAttribute("accept", this._options.acceptFiles);
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", this._options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ this._element.appendChild(input);
+
+ var self = this;
+ this._disposeSupport.attach(input, 'change', function(){
+ self._options.onChange(input);
+ });
+
+ this._disposeSupport.attach(input, 'mouseover', function(){
+ qq(self._element).addClass(self._options.hoverClass);
+ });
+ this._disposeSupport.attach(input, 'mouseout', function(){
+ qq(self._element).removeClass(self._options.hoverClass);
+ });
+ this._disposeSupport.attach(input, 'focus', function(){
+ qq(self._element).addClass(self._options.focusClass);
+ });
+ this._disposeSupport.attach(input, 'blur', function(){
+ qq(self._element).removeClass(self._options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+};
+qq.FineUploaderBasic = function(o){
+ var that = this;
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: false,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ stopOnFirstInvalidFile: true
+ },
+ callbacks: {
+ onSubmit: function(id, fileName){},
+ onComplete: function(id, fileName, responseJSON){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, fileName, loaded, total){},
+ onError: function(id, fileName, reason) {},
+ onAutoRetry: function(id, fileName, attemptNumber) {},
+ onManualRetry: function(id, fileName) {},
+ onValidateBatch: function(fileData) {},
+ onValidate: function(fileData) {}
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileName) {
+ if (fileName.length > 33) {
+ fileName = fileName.slice(0, 19) + '...' + fileName.slice(-14);
+ }
+ return fileName;
+ },
+ text: {
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ }
+ };
+
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ // number of files being uploaded
+ this._filesInProgress = [];
+
+ this._storedFileIds = [];
+
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+
+ this._paramsStore = this._createParamsStore();
+ this._endpointStore = this._createEndpointStore();
+
+ this._handler = this._createUploadHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader] ' + str, level);
+
+ }
+ },
+ setParams: function(params, fileId) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (fileId == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, fileId);
+ }
+ },
+ setEndpoint: function(endpoint, fileId) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (fileId == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, fileId);
+ }
+ },
+ getInProgress: function(){
+ return this._filesInProgress.length;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedFileIds.length) {
+ idToUpload = this._storedFileIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedFileIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(fileId) {
+ this._handler.cancel(fileId);
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedFileIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ },
+ addFiles: function(filesOrInputs) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ index, fileOrInput;
+
+ if (filesOrInputs) {
+ if (!window.FileList || !(filesOrInputs instanceof FileList)) {
+ filesOrInputs = [].concat(filesOrInputs);
+ }
+
+ for (index = 0; index < filesOrInputs.length; index+=1) {
+ fileOrInput = filesOrInputs[index];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
+ this._uploadFileList(verifiedFilesOrInputs);
+ }
+ },
+ getUuid: function(fileId) {
+ return this._handler.getUuid(fileId);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(fileId) {
+ return this._handler.getSize(fileId);
+ },
+ getFile: function(fileId) {
+ return this._handler.getFile(fileId);
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.isXhrUploadSupported(),
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, fileName, loaded, total){
+ self._onProgress(id, fileName, loaded, total);
+ self._options.callbacks.onProgress(id, fileName, loaded, total);
+ },
+ onComplete: function(id, fileName, result, xhr){
+ self._onComplete(id, fileName, result, xhr);
+ self._options.callbacks.onComplete(id, fileName, result);
+ },
+ onCancel: function(id, fileName){
+ self._onCancel(id, fileName);
+ self._options.callbacks.onCancel(id, fileName);
+ },
+ onUpload: function(id, fileName){
+ self._onUpload(id, fileName);
+ self._options.callbacks.onUpload(id, fileName);
+ },
+ onUploadChunk: function(id, fileName, chunkData){
+ self._options.callbacks.onUploadChunk(id, fileName, chunkData);
+ },
+ onResume: function(id, fileName, chunkData) {
+ return self._options.callbacks.onResume(id, fileName, chunkData);
+ },
+ onAutoRetry: function(id, fileName, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, fileName, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, fileName, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, fileName, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, fileName);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, fileName, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, fileName){
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, fileName, loaded, total){
+ },
+ _onComplete: function(id, fileName, result, xhr){
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, fileName, result, xhr);
+ },
+ _onCancel: function(id, fileName){
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedFileIndex = qq.indexOf(this._storedFileIds, id);
+ if (!this._options.autoUpload && storedFileIndex >= 0) {
+ this._storedFileIds.splice(storedFileIndex, 1);
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, fileName){},
+ _onInputChange: function(input){
+ if (qq.isXhrUploadSupported()){
+ this.addFiles(input.files);
+ } else {
+ this.addFiles(input);
+ }
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, fileName) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + fileName + "...");
+ },
+ _onAutoRetry: function(id, fileName, responseJSON) {
+ this.log("Retrying " + fileName + "...");
+ this._autoRetries[id]++;
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, fileName, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, fileName, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, fileName, "XHR returned response code " + xhr.status);
+ }
+ else {
+ var errorReason = response.error ? response.error : "Upload failure reason unknown";
+ this._options.callbacks.onError(id, fileName, errorReason);
+ }
+ }
+ },
+ _uploadFileList: function(files){
+ var validationDescriptors, index, batchInvalid;
+
+ validationDescriptors = this._getValidationDescriptors(files);
+ batchInvalid = this._options.callbacks.onValidateBatch(validationDescriptors) === false;
+
+ if (!batchInvalid) {
+ if (files.length > 0) {
+ for (index = 0; index < files.length; index++){
+ if (this._validateFile(files[index])){
+ this._uploadFile(files[index]);
+ } else {
+ if (this._options.validation.stopOnFirstInvalidFile){
+ return;
+ }
+ }
+ }
+ }
+ else {
+ this._error('noFilesError', "");
+ }
+ }
+ },
+ _uploadFile: function(fileContainer){
+ var id = this._handler.add(fileContainer);
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onSubmit(id, fileName) !== false){
+ this._onSubmit(id, fileName);
+ if (this._options.autoUpload) {
+ this._handler.upload(id);
+ }
+ else {
+ this._storeFileForLater(id);
+ }
+ }
+ },
+ _storeFileForLater: function(id) {
+ this._storedFileIds.push(id);
+ },
+ _validateFile: function(file){
+ var validationDescriptor, name, size;
+
+ validationDescriptor = this._getValidationDescriptor(file);
+ name = validationDescriptor.name;
+ size = validationDescriptor.size;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ return false;
+ }
+
+ if (!this._isAllowedExtension(name)){
+ this._error('typeError', name);
+ return false;
+
+ }
+ else if (size === 0){
+ this._error('emptyError', name);
+ return false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._error('sizeError', name);
+ return false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._error('minSizeError', name);
+ return false;
+ }
+
+ return true;
+ },
+ _error: function(code, fileName){
+ var message = this._options.messages[code];
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ var extensions = this._options.validation.allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(fileName));
+ r('{extensions}', extensions);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ this._options.callbacks.onError(null, fileName, message);
+
+ return message;
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1024;
+ i++;
+ } while (bytes > 99);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ }
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ }
+ }());
+ }
+ },
+ _parseFileName: function(file) {
+ var name;
+
+ if (file.value){
+ // it is a file input
+ // get input value and remove path to normalize
+ name = file.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+
+ return name;
+ },
+ _parseFileSize: function(file) {
+ var size;
+
+ if (!file.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (file.fileSize !== null && file.fileSize !== undefined) ? file.fileSize : file.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(file) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileName(file);
+ size = this._parseFileSize(file);
+
+ fileDescriptor.name = name;
+ if (size) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function() {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, fileId) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[fileId] = paramsCopy;
+ },
+
+ getParams: function(fileId) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (fileId != null && paramsStore[fileId]) {
+ qq.extend(paramsCopy, paramsStore[fileId]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options.request.params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function() {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, fileId) {
+ endpointStore[fileId] = endpoint;
+ },
+
+ getEndpoint: function(fileId) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (fileId != null && endpointStore[fileId]) {
+ return endpointStore[fileId];
+ }
+
+ return self._options.request.endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz, dirPending,
+ droppedFiles = [],
+ droppedEntriesCount = 0,
+ droppedEntriesParsedCount = 0,
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropArea: null,
+ extraDropzones: [],
+ hideDropzones: true,
+ multiple: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {},
+ error: function(code, filename) {},
+ log: function(message, level) {}
+ }
+ };
+
+ qq.extend(options, o);
+
+ function maybeUploadDroppedFiles() {
+ if (droppedEntriesCount === droppedEntriesParsedCount && !dirPending) {
+ options.callbacks.log('Grabbed ' + droppedFiles.length + " files after tree traversal.");
+ dz.dropDisabled(false);
+ options.callbacks.dropProcessing(false, droppedFiles);
+ }
+ }
+ function addDroppedFile(file) {
+ droppedFiles.push(file);
+ droppedEntriesParsedCount+=1;
+ maybeUploadDroppedFiles();
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i;
+
+ droppedEntriesCount+=1;
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ addDroppedFile(file);
+ });
+ }
+ else if (entry.isDirectory) {
+ dirPending = true;
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ droppedEntriesParsedCount+=1;
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]);
+ }
+
+ dirPending = false;
+
+ if (!entries.length) {
+ maybeUploadDroppedFiles();
+ }
+ });
+ }
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry;
+
+ options.callbacks.dropProcessing(true);
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.multiple) {
+ options.callbacks.dropProcessing(false);
+ options.callbacks.error('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ }
+ else {
+ droppedFiles = [];
+ droppedEntriesCount = 0;
+ droppedEntriesParsedCount = 0;
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ if (i === items.length-1) {
+ maybeUploadDroppedFiles();
+ }
+ }
+
+ else {
+ traverseFileTree(entry);
+ }
+ }
+ }
+ }
+ else {
+ options.callbacks.dropProcessing(false, dataTransfer.files);
+ dz.dropDisabled(false);
+ }
+ }
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer);
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ if (options.dropArea) {
+ options.extraDropzones.push(options.dropArea);
+ }
+
+ var i, dropzones = options.extraDropzones;
+
+ for (i=0; i < dropzones.length; i+=1){
+ setupDropzone(dropzones[i]);
+ }
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (options.dropArea && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ if (qq(options.dropArea).hasClass(options.classes.dropDisabled)) {
+ return;
+ }
+
+ options.dropArea.style.display = 'block';
+ for (i=0; i < dropzones.length; i+=1) {
+ dropzones[i].style.display = 'block';
+ }
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropzones && qq.FineUploader.prototype._leaving_document_out(e)) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropzones) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setup: function() {
+ setupDragDrop();
+ },
+
+ setupExtraDropzone: function(element) {
+ options.extraDropzones.push(element);
+ setupDropzone(element);
+ },
+
+ removeExtraDropzone: function(element) {
+ var i, dzs = options.extraDropzones;
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '
' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ dropDisabled: 'qq-upload-drop-area-disabled',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file"
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ alert(message);
+ }, 0);
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeExtraDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ cancel: function(fileId) {
+ qq.FineUploaderBasic.prototype.cancel.apply(this, arguments);
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dnd, preventSelectFiles, defaultDropAreaEl;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ defaultDropAreaEl = this._find(this._options.element, 'drop');
+ }
+
+ dnd = new qq.DragAndDrop({
+ dropArea: defaultDropAreaEl,
+ extraDropzones: this._options.dragAndDrop.extraDropzones,
+ hideDropzones: this._options.dragAndDrop.hideDropzones,
+ multiple: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {
+ var input = self._button.getInput();
+
+ if (isProcessing) {
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ }
+ else {
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+ }
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ error: function(code, filename) {
+ self._error(code, filename);
+ },
+ log: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+
+ dnd.setup();
+
+ return dnd;
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeFileForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeFileForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type){
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, fileName){
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, fileName);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, fileName, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, text, percent, cancelLink, size;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, just display final size
+ text = this._formatSize(total);
+ }
+ else {
+ // If still uploading, display percentage
+ text = this._formatProgress(loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+
+ size = this._find(item, 'size');
+ qq(size).css({display: 'inline'});
+ qq(size).setText(text);
+ },
+ _onComplete: function(id, fileName, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success){
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, fileName){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+ this._showSpinner(item);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, cancelLink, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ var item = this.getItemByFileId(id);
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(item);
+ this._showCancelLink(item);
+ return true;
+ }
+ return false;
+ },
+ _addToList: function(id, fileName){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(fileName));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) this._clearList();
+ this._listElement.appendChild(item);
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId == undefined) {
+ item = target = target.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ //TODO turn this into a real tooltip, with click trigger (so it is usable on mobile devices). See case #355 for details.
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(item) {
+ var spinnerEl = this._find(item, 'spinner');
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+ cancelLink.style.display = 'inline';
+ }
+ },
+ _error: function(code, fileName){
+ var message = qq.FineUploaderBasic.prototype._error.apply(this, arguments);
+ this._options.showMessage(message);
+ }
+});
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, dequeue, handlerImpl;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ dequeue = function(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ };
+
+ if (qq.isXhrUploadSupported()) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, log);
+ }
+
+
+ return {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ return handlerImpl.upload(id);
+ }
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id){
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ handlerImpl.cancel(id);
+ dequeue(id);
+ },
+ /**
+ * Cancels all uploads
+ */
+ cancelAll: function(){
+ qq.each(queue, function(idx, fileId) {
+ this.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ /**
+ * Returns id of files being uploaded or
+ * waiting for their turn
+ */
+ getQueue: function(){
+ return queue;
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ queue = [];
+ handlerImpl.reset();
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+};
+/*globals qq, document, setTimeout*/
+/*jslint white: true*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ api;
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHTML = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHTML);
+ //plain text response may be wrapped in tag
+ if (innerHTML && innerHTML.match(/^ ');
+ // src="javascript:false;" removes ie6 prompt on https
+
+ iframe.setAttribute('id', id);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ qq.UploadHandler.prototype.reset.apply(this, arguments);
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ options.onCancel(id, this.getName(id));
+
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ var iframe = document.getElementById(id);
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ },
+ upload: function(id){
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form = createForm(id, iframe);
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, this.getName(id));
+
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(){
+ log('iframe loaded');
+
+ var response = getIframeContentJson(iframe);
+
+ // timeout added to fix busy state in FF3.6
+ setTimeout(function(){
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ qq(iframe).remove();
+ }, 1);
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+
+ return id;
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.isFileChunkingSupported(),
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.areCookiesEnabled(),
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.end - chunkData.start;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(file, startByte, endByte) {
+ if (file.slice) {
+ return file.slice(startByte, endByte);
+ }
+ else if (file.mozSlice) {
+ return file.mozSlice(startByte, endByte);
+ }
+ else if (file.webkitSlice) {
+ return file.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ file = fileState[id].file,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(file, startBytes, endBytes)
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ fileState[id].xhr = new XMLHttpRequest();
+ return fileState[id].xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ protocol = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id);
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ params[options.inputName] = name;
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(protocol, url, true);
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ name = api.getName(id),
+ file = fileState[id].file;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", file.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedFile(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+
+ options.onComplete(id, name, response, xhr);
+ delete fileState[id].xhr;
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkData = getChunkData(id, fileState[id].remainingChunkIdxs[0]),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ persistChunkData(id, chunkData);
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ if (fileState[id].loaded < size) {
+ var totalLoaded = e.loaded + fileState[id].loaded;
+ options.onProgress(id, name, totalLoaded, size);
+ }
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.end - chunkData.start;
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ deletePersistedChunkData(id);
+ handleCompletedFile(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for file ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for file ID " + id + " - starting from the first chunk", 'error');
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedFile(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedFile(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid + cookieItemDelimiter + chunkData.part,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ var cookieName = getChunkDataCookieName(id);
+
+ qq.deleteCookie(cookieName);
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ delimiterIndex, uuid, partIndex;
+
+ if (chunkCookieValue) {
+ delimiterIndex = chunkCookieValue.indexOf(cookieItemDelimiter);
+ uuid = chunkCookieValue.substr(0, delimiterIndex);
+ partIndex = parseInt(chunkCookieValue.substr(delimiterIndex + 1, chunkCookieValue.length - delimiterIndex), 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex
+ };
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var name = api.getName(id),
+ firstChunkIndex = 0,
+ persistedChunkInfoForResume, firstChunkDataForResume, currentChunkIndex;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part);
+ if (options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume)) !== false) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].uuid = persistedChunkInfoForResume.uuid;
+ fileState[id].loaded = firstChunkDataForResume.start;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+ }
+ }
+ }
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function handleStandardFileUpload(id) {
+ var file = fileState[id].file,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, file, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+
+ api = {
+ /**
+ * Adds file to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(file){
+ if (!(file instanceof File)){
+ throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
+ }
+
+
+ var id = fileState.push({file: file}) - 1;
+ fileState[id].uuid = qq.getUniqueId();
+
+ return id;
+ },
+ getName: function(id){
+ var file = fileState[id].file;
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var file = fileState[id].file;
+ return file.fileSize != null ? file.fileSize : file.size;
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file;
+ }
+ },
+ /**
+ * Returns uploaded bytes for file identified by id
+ */
+ getLoaded: function(id){
+ return fileState[id].loaded || 0;
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry){
+ var name = this.getName(id);
+
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ },
+ cancel: function(id){
+ options.onCancel(id, this.getName(id));
+
+ if (fileState[id].xhr){
+ fileState[id].xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var uploader, $el, init, dataStore, pluginOption, pluginOptions, addCallbacks, transformVariables, isValidCommand,
+ delegateCommand;
+
+ pluginOptions = ['uploaderType'];
+
+ init = function (options) {
+ if (options) {
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+
+ if (pluginOption('uploaderType') === 'basic') {
+ uploader(new qq.FineUploaderBasic(xformedOpts));
+ }
+ else {
+ uploader(new qq.FineUploader(xformedOpts));
+ }
+ }
+
+ return $el;
+ };
+
+ dataStore = function(key, val) {
+ var data = $el.data('fineuploader');
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data('fineuploader', data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ //the underlying Fine Uploader instance is stored in jQuery's data stored, associated with the element
+ // tied to this instance of the plug-in
+ uploader = function(instanceToStore) {
+ return dataStore('uploader', instanceToStore);
+ };
+
+ pluginOption = function(option, optionVal) {
+ return dataStore(option, optionVal);
+ };
+
+ //implement all callbacks defined in Fine Uploader as functions that trigger appropriately names events and
+ // return the result of executing the bound handler back to Fine Uploader
+ addCallbacks = function(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {};
+
+ $.each(new qq.FineUploaderBasic()._options.callbacks, function(prop, func) {
+ var name, $callbackEl;
+
+ name = /^on(\w+)/.exec(prop)[1];
+ name = name.substring(0, 1).toLowerCase() + name.substring(1);
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var args = Array.prototype.slice.call(arguments);
+ return $callbackEl.triggerHandler(name, args);
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ transformVariables = function(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ if (source.uploaderType !== 'basic') {
+ xformed = { element : $el[0] };
+ }
+ else {
+ xformed = {};
+ }
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if ($.inArray(prop, pluginOptions) >= 0) {
+ pluginOption(prop, val);
+ }
+ else if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ isValidCommand = function(command) {
+ return $.type(command) === "string" &&
+ !command.match(/^_/) && //enforce private methods convention
+ uploader()[command] !== undefined;
+ };
+
+ //assuming we have already verified that this is a valid command, call the associated function in the underlying
+ // Fine Uploader instance (passing along the arguments from the caller) and return the result of the call back to the caller
+ delegateCommand = function(command) {
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
+
+ transformVariables(origArgs, xformedArgs);
+
+ return uploader()[command].apply(uploader(), xformedArgs);
+ };
+
+ $.fn.fineUploader = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (uploader() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error('Method ' + optionsOrCommand + ' does not exist on jQuery.fineUploader');
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
diff --git a/ajax/libs/file-uploader/3.2.0/fineuploader-jquery.min.js b/ajax/libs/file-uploader/3.2.0/fineuploader-jquery.min.js
new file mode 100644
index 000000000..311a3e087
--- /dev/null
+++ b/ajax/libs/file-uploader/3.2.0/fineuploader-jquery.min.js
@@ -0,0 +1,13 @@
+/**
+ * http://github.com/Valums-File-Uploader/file-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Original version: 1.0 © 2010 Andrew Valums ( andrew(at)valums.com )
+ * Current Maintainer (2.0+): © 2012, Ray Nicholus ( fineuploader(at)garstasio.com )
+ *
+ * Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!==null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable!==null&&variable&&typeof variable==="object"&&variable.constructor===Object};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isFileOrInput=function(maybeFileOrInput){"use strict";if(window.File&&maybeFileOrInput instanceof File){return true}else if(window.HTMLInputElement){if(maybeFileOrInput instanceof HTMLInputElement){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}else if(maybeFileOrInput.tagName){if(maybeFileOrInput.tagName.toLowerCase()==="input"){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}return false};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice||File.prototype.webkitSlice||File.prototype.mozSlice)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}})};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileName=fileName.slice(0,19)+"..."+fileName.slice(-14)}return fileName},text:{sizeSymbols:["kB","MB","GB","TB","PB","EB"]}};qq.extend(this._options,o,true);this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedFileIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._paramsStore=this._createParamsStore();this._endpointStore=this._createEndpointStore();this._handler=this._createUploadHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader] "+str,level)}},setParams:function(params,fileId){if(fileId==null){this._options.request.params=params}else{this._paramsStore.setParams(params,fileId)}},setEndpoint:function(endpoint,fileId){if(fileId==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,fileId)}},getInProgress:function(){return this._filesInProgress.length},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedFileIds.length){idToUpload=this._storedFileIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedFileIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._handler.retry(id);return true}else{return false}},cancel:function(fileId){this._handler.cancel(fileId)},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedFileIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset()},addFiles:function(filesOrInputs){var self=this,verifiedFilesOrInputs=[],index,fileOrInput;if(filesOrInputs){if(!window.FileList||!(filesOrInputs instanceof FileList)){filesOrInputs=[].concat(filesOrInputs)}for(index=0;index=0){this._storedFileIds.splice(storedFileIndex,1)}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,fileName){},_onInputChange:function(input){if(qq.isXhrUploadSupported()){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,fileName){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+fileName+"...")},_onAutoRetry:function(id,fileName,responseJSON){this.log("Retrying "+fileName+"...");this._autoRetries[id]++;this._handler.retry(id)},_shouldAutoRetry:function(id,fileName,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0){for(index=0;indexthis._options.validation.sizeLimit){this._error("sizeError",name);return false}else if(size&&size99);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileName:function(file){var name;if(file.value){name=file.value.replace(/.*(\/|\\)/,"")}else{name=file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}return name},_parseFileSize:function(file){var size;if(!file.value){size=file.fileSize!==null&&file.fileSize!==undefined?file.fileSize:file.size}return size},_getValidationDescriptor:function(file){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileName(file);size=this._parseFileSize(file);fileDescriptor.name=name;if(size){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(){var paramsStore={},self=this;return{setParams:function(params,fileId){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[fileId]=paramsCopy},getParams:function(fileId){var paramsCopy={};if(fileId!=null&¶msStore[fileId]){qq.extend(paramsCopy,paramsStore[fileId])}else{qq.extend(paramsCopy,self._options.request.params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(){var endpointStore={},self=this;return{setEndpoint:function(endpoint,fileId){endpointStore[fileId]=endpoint},getEndpoint:function(fileId){if(fileId!=null&&endpointStore[fileId]){return endpointStore[fileId]}return self._options.request.endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,dirPending,droppedFiles=[],droppedEntriesCount=0,droppedEntriesParsedCount=0,disposeSupport=new qq.DisposeSupport;options={dropArea:null,extraDropzones:[],hideDropzones:true,multiple:true,classes:{dropActive:null},callbacks:{dropProcessing:function(isProcessing,files){},error:function(code,filename){},log:function(message,level){}}};qq.extend(options,o);function maybeUploadDroppedFiles(){if(droppedEntriesCount===droppedEntriesParsedCount&&!dirPending){options.callbacks.log("Grabbed "+droppedFiles.length+" files after tree traversal.");dz.dropDisabled(false);options.callbacks.dropProcessing(false,droppedFiles)}}function addDroppedFile(file){droppedFiles.push(file);droppedEntriesParsedCount+=1;maybeUploadDroppedFiles()}function traverseFileTree(entry){var dirReader,i;droppedEntriesCount+=1;if(entry.isFile){entry.file(function(file){addDroppedFile(file)})}else if(entry.isDirectory){dirPending=true;dirReader=entry.createReader();dirReader.readEntries(function(entries){droppedEntriesParsedCount+=1;for(i=0;i1&&!options.multiple){options.callbacks.dropProcessing(false);options.callbacks.error("tooManyFilesError","");dz.dropDisabled(false)}else{droppedFiles=[];droppedEntriesCount=0;droppedEntriesParsedCount=0;if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",dropDisabled:"qq-upload-drop-area-disabled",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file"},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},showMessage:function(message){setTimeout(function(){alert(message)
+},0)}},true);qq.extend(this._options,o,true);this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop()};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeExtraDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},cancel:function(fileId){qq.FineUploaderBasic.prototype.cancel.apply(this,arguments);var item=this.getItemByFileId(fileId);qq(item).remove()},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dnd,preventSelectFiles,defaultDropAreaEl;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){defaultDropAreaEl=this._find(this._options.element,"drop")}dnd=new qq.DragAndDrop({dropArea:defaultDropAreaEl,extraDropzones:this._options.dragAndDrop.extraDropzones,hideDropzones:this._options.dragAndDrop.hideDropzones,multiple:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{dropProcessing:function(isProcessing,files){var input=self._button.getInput();if(isProcessing){qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)}else{qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles)}if(files){self.addFiles(files)}},error:function(code,filename){self._error(code,filename)},log:function(message,level){self.log(message,level)}}});dnd.setup();return dnd},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeFileForLater:function(id){qq.FineUploaderBasic.prototype._storeFileForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,fileName){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,fileName)},_onProgress:function(id,fileName,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,text,percent,cancelLink,size;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);text=this._formatSize(total)}else{text=this._formatProgress(loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"});size=this._find(item,"size");qq(size).css({display:"inline"});qq(size).setText(text)},_onComplete:function(id,fileName,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,fileName){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);var item=this.getItemByFileId(id);this._showSpinner(item)},_onBeforeAutoRetry:function(id){var item,progressBar,cancelLink,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){var item=this.getItemByFileId(id);this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(item);this._showCancelLink(item);return true}return false},_addToList:function(id,fileName){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(fileName));qq(this._find(item,"size")).hide();if(!this._options.multiple)this._clearList();this._listElement.appendChild(item)},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId==undefined){item=target=target.parentNode}if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(item){var spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");cancelLink.style.display="inline"}},_error:function(code,fileName){var message=qq.FineUploaderBasic.prototype._error.apply(this,arguments);this._options.showMessage(message)}});qq.UploadHandler=function(o){"use strict";var queue=[],options,log,dequeue,handlerImpl;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){}};qq.extend(options,o);log=options.log;dequeue=function(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){log("Cancelling "+id);options.paramsStore.remove(id);handlerImpl.cancel(id);dequeue(id)},cancelAll:function(){qq.each(queue,function(idx,fileId){this.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},getQueue:function(){return queue},reset:function(){log("Resetting upload handler");queue=[];handlerImpl.reset()},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}}};qq.UploadHandlerForm=function(o,uploadCompleteCallback,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},uploadComplete=uploadCompleteCallback,log=logCallback,api;function attachLoadEvent(iframe,callback){detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}function getIframeContentJson(iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHTML=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHTML);if(innerHTML&&innerHTML.match(/^ ');iframe.setAttribute("id",id);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){return inputs[id].value.replace(/.*(\/|\\)/,"")},isValid:function(id){return inputs[id]!==undefined},reset:function(){qq.UploadHandler.prototype.reset.apply(this,arguments);inputs=[];uuids=[];detachLoadEvents={}},getUuid:function(id){return uuids[id]},cancel:function(id){options.onCancel(id,this.getName(id));delete inputs[id];delete uuids[id];delete detachLoadEvents[id];var iframe=document.getElementById(id);if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form=createForm(id,iframe);if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,this.getName(id));form.appendChild(input);attachLoadEvent(iframe,function(){log("iframe loaded");var response=getIframeContentJson(iframe);setTimeout(function(){detachLoadEvents[id]();delete detachLoadEvents[id];qq(iframe).remove()},1);if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove();return id}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,logCallback){"use strict";var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.isFileChunkingSupported(),resumeEnabled=options.resume.enabled&&chunkFiles&&qq.areCookiesEnabled(),resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.end-chunkData.start;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(file,startByte,endByte){if(file.slice){return file.slice(startByte,endByte)}else if(file.mozSlice){return file.mozSlice(startByte,endByte)}else if(file.webkitSlice){return file.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),file=fileState[id].file,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(file,startBytes,endBytes)}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){fileState[id].xhr=new XMLHttpRequest;return fileState[id].xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,protocol=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id);params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size}if(!options.paramsInBody){params[options.inputName]=name;url=qq.obj2url(params,endpoint)}xhr.open(protocol,url,true);if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,name=api.getName(id),file=fileState[id].file;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",file.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedFile(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);delete fileState[id].xhr;uploadComplete(id)}function uploadNextChunk(id){var chunkData=getChunkData(id,fileState[id].remainingChunkIdxs[0]),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}persistChunkData(id,chunkData);xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){if(fileState[id].loaded0){uploadNextChunk(id)}else{deletePersistedChunkData(id);handleCompletedFile(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(xhr){var response;try{response=qq.parseJson(xhr.responseText)}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for file ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id)}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for file ID "+id+" - starting from the first chunk","error");api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedFile(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedFile(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),delimiterIndex,uuid,partIndex;if(chunkCookieValue){delimiterIndex=chunkCookieValue.indexOf(cookieItemDelimiter);uuid=chunkCookieValue.substr(0,delimiterIndex);partIndex=parseInt(chunkCookieValue.substr(delimiterIndex+1,chunkCookieValue.length-delimiterIndex),10);return{uuid:uuid,part:partIndex}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function handleFileChunkingUpload(id,retry){var name=api.getName(id),firstChunkIndex=0,persistedChunkInfoForResume,firstChunkDataForResume,currentChunkIndex;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part);if(options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume))!==false){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].uuid=persistedChunkInfoForResume.uuid;fileState[id].loaded=firstChunkDataForResume.start;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex)}}}for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}}uploadNextChunk(id)}function handleStandardFileUpload(id){var file=fileState[id].file,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,file,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}api={add:function(file){if(!(file instanceof File)){throw new Error("Passed obj in not a File (in qq.UploadHandlerXhr)")}var id=fileState.push({file:file})-1;fileState[id].uuid=qq.getUniqueId();return id},getName:function(id){var file=fileState[id].file;return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name},getSize:function(id){var file=fileState[id].file;return file.fileSize!=null?file.fileSize:file.size},getFile:function(id){if(fileState[id]){return fileState[id].file}},getLoaded:function(id){return fileState[id].loaded||0},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}},cancel:function(id){options.onCancel(id,this.getName(id));if(fileState[id].xhr){fileState[id].xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};!function($){"use strict";var uploader,$el,init,dataStore,pluginOption,pluginOptions,addCallbacks,transformVariables,isValidCommand,delegateCommand;pluginOptions=["uploaderType"];init=function(options){if(options){var xformedOpts=transformVariables(options);addCallbacks(xformedOpts);if(pluginOption("uploaderType")==="basic"){uploader(new qq.FineUploaderBasic(xformedOpts))}else{uploader(new qq.FineUploader(xformedOpts))}}return $el};dataStore=function(key,val){var data=$el.data("fineuploader");if(val){if(data===undefined){data={}}data[key]=val;$el.data("fineuploader",data)}else{if(data===undefined){return null}return data[key]}};uploader=function(instanceToStore){return dataStore("uploader",instanceToStore)};pluginOption=function(option,optionVal){return dataStore(option,optionVal)};addCallbacks=function(transformedOpts){var callbacks=transformedOpts.callbacks={};$.each((new qq.FineUploaderBasic)._options.callbacks,function(prop,func){var name,$callbackEl;name=/^on(\w+)/.exec(prop)[1];name=name.substring(0,1).toLowerCase()+name.substring(1);$callbackEl=$el;callbacks[prop]=function(){var args=Array.prototype.slice.call(arguments);return $callbackEl.triggerHandler(name,args)}})};transformVariables=function(source,dest){var xformed,arrayVals;if(dest===undefined){if(source.uploaderType!=="basic"){xformed={element:$el[0]}}else{xformed={}}}else{xformed=dest}$.each(source,function(prop,val){if($.inArray(prop,pluginOptions)>=0){pluginOption(prop,val)}else if(val instanceof $){xformed[prop]=val[0]}else if($.isPlainObject(val)){xformed[prop]={};transformVariables(val,xformed[prop])}else if($.isArray(val)){arrayVals=[];$.each(val,function(idx,arrayVal){if(arrayVal instanceof $){$.merge(arrayVals,arrayVal)}else{arrayVals.push(arrayVal)}});xformed[prop]=arrayVals}else{xformed[prop]=val}});if(dest===undefined){return xformed}};isValidCommand=function(command){return $.type(command)==="string"&&!command.match(/^_/)&&uploader()[command]!==undefined};delegateCommand=function(command){var xformedArgs=[],origArgs=Array.prototype.slice.call(arguments,1);transformVariables(origArgs,xformedArgs);return uploader()[command].apply(uploader(),xformedArgs)};$.fn.fineUploader=function(optionsOrCommand){var self=this,selfArgs=arguments,retVals=[];this.each(function(index,el){$el=$(el);if(uploader()&&isValidCommand(optionsOrCommand)){retVals.push(delegateCommand.apply(self,selfArgs));if(self.length===1){return false}}else if(typeof optionsOrCommand==="object"||!optionsOrCommand){init.apply(self,selfArgs)}else{$.error("Method "+optionsOrCommand+" does not exist on jQuery.fineUploader")}});if(retVals.length===1){return retVals[0]}else if(retVals.length>1){return retVals}return this}}(jQuery);
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.2.0/fineuploader.css b/ajax/libs/file-uploader/3.2.0/fineuploader.css
new file mode 100644
index 000000000..f8a14fdf1
--- /dev/null
+++ b/ajax/libs/file-uploader/3.2.0/fineuploader.css
@@ -0,0 +1,148 @@
+/*
+ * Original version: 1.0 © 2010 Andrew Valums ( andrew(at)valums.com )
+ * Current Maintainer (2.0+): 2012, Ray Nicholus ( fineuploader(at)garstasio.com )
+ *
+ * Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
+ */
+.qq-uploader {
+ position: relative;
+ width: 100%;
+}
+.qq-upload-button {
+ display: block;
+ width: 105px;
+ padding: 7px 0;
+ text-align: center;
+ background: #880000;
+ border-bottom: 1px solid #DDD;
+ color: #FFF;
+}
+.qq-upload-button-hover {
+ background: #CC0000;
+}
+.qq-upload-button-focus {
+ outline: 1px dotted #000000;
+}
+.qq-upload-drop-area, .qq-upload-extra-drop-area {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ min-height: 30px;
+ z-index: 2;
+ background: #FF9797;
+ text-align: center;
+}
+.qq-upload-drop-area span {
+ display: block;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ margin-top: -8px;
+ font-size: 16px;
+}
+.qq-upload-extra-drop-area {
+ position: relative;
+ margin-top: 50px;
+ font-size: 16px;
+ padding-top: 30px;
+ height: 20px;
+ min-height: 40px;
+}
+.qq-upload-drop-area-active {
+ background: #FF7171;
+}
+.qq-upload-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+.qq-upload-list li {
+ margin: 0;
+ padding: 9px;
+ line-height: 15px;
+ font-size: 16px;
+ background-color: #FFF0BD;
+}
+.qq-upload-file, .qq-upload-spinner, .qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text, .qq-upload-finished {
+ margin-right: 12px;
+}
+.qq-upload-file {
+}
+.qq-upload-spinner {
+ display: inline-block;
+ background: url("loading.gif");
+ width: 15px;
+ height: 15px;
+ vertical-align: text-bottom;
+}
+.qq-drop-processing {
+ display: none;
+}
+.qq-drop-processing-spinner {
+ display: inline-block;
+ background: url("processing.gif");
+ width: 24px;
+ height: 24px;
+ vertical-align: text-bottom;
+}
+.qq-upload-finished {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-retry {
+ display: none;
+ color: #000000;
+}
+.qq-upload-cancel {
+ color: #000000;
+}
+.qq-upload-retryable .qq-upload-retry {
+ display: inline;
+}
+.qq-upload-size, .qq-upload-cancel, .qq-upload-retry {
+ font-size: 12px;
+ font-weight: normal;
+}
+.qq-upload-failed-text {
+ display: none;
+ font-style: italic;
+ font-weight: bold;
+}
+.qq-upload-failed-icon {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-fail .qq-upload-failed-text {
+ display: inline;
+}
+.qq-upload-retrying .qq-upload-failed-text {
+ display: inline;
+ color: #D60000;
+}
+.qq-upload-list li.qq-upload-success {
+ background-color: #5DA30C;
+ color: #FFFFFF;
+}
+.qq-upload-list li.qq-upload-fail {
+ background-color: #D60000;
+ color: #FFFFFF;
+}
+.qq-progress-bar {
+ background: -moz-linear-gradient(top, rgba(30,87,153,1) 0%, rgba(41,137,216,1) 50%, rgba(32,124,202,1) 51%, rgba(125,185,232,1) 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(30,87,153,1)), color-stop(50%,rgba(41,137,216,1)), color-stop(51%,rgba(32,124,202,1)), color-stop(100%,rgba(125,185,232,1))); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* IE10+ */
+ background: linear-gradient(to bottom, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* W3C */
+ width: 0%;
+ height: 15px;
+ border-radius: 6px;
+ margin-bottom: 3px;
+ display: none;
+}
diff --git a/ajax/libs/file-uploader/3.2.0/fineuploader.js b/ajax/libs/file-uploader/3.2.0/fineuploader.js
new file mode 100644
index 000000000..e0e7319af
--- /dev/null
+++ b/ajax/libs/file-uploader/3.2.0/fineuploader.js
@@ -0,0 +1,3041 @@
+/**
+ * http://github.com/Valums-File-Uploader/file-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Original version: 1.0 © 2010 Andrew Valums ( andrew(at)valums.com )
+ * Current Maintainer (2.0+): © 2012, Ray Nicholus ( fineuploader(at)garstasio.com )
+ *
+ * Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity !== null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+ if (window.File && maybeFileOrInput instanceof File) {
+ return true;
+ }
+ else if (window.HTMLInputElement) {
+ if (maybeFileOrInput instanceof HTMLInputElement) {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ else if (maybeFileOrInput.tagName) {
+ if (maybeFileOrInput.tagName.toLowerCase() === 'input') {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice || File.prototype.webkitSlice || File.prototype.mozSlice);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(obj, callback) {
+ "use strict";
+ var key, retVal;
+ if (obj) {
+ for (key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ retVal = callback(key, obj[key]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(encodeURIComponent(key), encodeURIComponent(val()));
+ }
+ else {
+ formData.append(encodeURIComponent(key), encodeURIComponent(val));
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ c;
+
+ for(var i=0;i < ca.length;i++) {
+ c = ca[i];
+ while (c.charAt(0)==' ') {
+ c = c.substring(1,c.length);
+ }
+ if (c.indexOf(nameEQ) === 0) {
+ return c.substring(nameEQ.length,c.length);
+ }
+ }
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = cookie.trim();
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (typeof JSON.parse === "function") {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+qq.UploadButton = function(o){
+ this._options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input){},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ qq.extend(this._options, o);
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._element = this._options.element;
+
+ // make button suitable container for input
+ qq(this._element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ this._input = this._createInput();
+};
+
+qq.UploadButton.prototype = {
+ /* returns file input element */
+ getInput: function(){
+ return this._input;
+ },
+ /* cleans/recreates the file input */
+ reset: function(){
+ if (this._input.parentNode){
+ qq(this._input).remove();
+ }
+
+ qq(this._element).removeClass(this._options.focusClass);
+ this._input = this._createInput();
+ },
+ _createInput: function(){
+ var input = document.createElement("input");
+
+ if (this._options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (this._options.acceptFiles) input.setAttribute("accept", this._options.acceptFiles);
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", this._options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ this._element.appendChild(input);
+
+ var self = this;
+ this._disposeSupport.attach(input, 'change', function(){
+ self._options.onChange(input);
+ });
+
+ this._disposeSupport.attach(input, 'mouseover', function(){
+ qq(self._element).addClass(self._options.hoverClass);
+ });
+ this._disposeSupport.attach(input, 'mouseout', function(){
+ qq(self._element).removeClass(self._options.hoverClass);
+ });
+ this._disposeSupport.attach(input, 'focus', function(){
+ qq(self._element).addClass(self._options.focusClass);
+ });
+ this._disposeSupport.attach(input, 'blur', function(){
+ qq(self._element).removeClass(self._options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+};
+qq.FineUploaderBasic = function(o){
+ var that = this;
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: false,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ stopOnFirstInvalidFile: true
+ },
+ callbacks: {
+ onSubmit: function(id, fileName){},
+ onComplete: function(id, fileName, responseJSON){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, fileName, loaded, total){},
+ onError: function(id, fileName, reason) {},
+ onAutoRetry: function(id, fileName, attemptNumber) {},
+ onManualRetry: function(id, fileName) {},
+ onValidateBatch: function(fileData) {},
+ onValidate: function(fileData) {}
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileName) {
+ if (fileName.length > 33) {
+ fileName = fileName.slice(0, 19) + '...' + fileName.slice(-14);
+ }
+ return fileName;
+ },
+ text: {
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ }
+ };
+
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ // number of files being uploaded
+ this._filesInProgress = [];
+
+ this._storedFileIds = [];
+
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+
+ this._paramsStore = this._createParamsStore();
+ this._endpointStore = this._createEndpointStore();
+
+ this._handler = this._createUploadHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader] ' + str, level);
+
+ }
+ },
+ setParams: function(params, fileId) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (fileId == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, fileId);
+ }
+ },
+ setEndpoint: function(endpoint, fileId) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (fileId == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, fileId);
+ }
+ },
+ getInProgress: function(){
+ return this._filesInProgress.length;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedFileIds.length) {
+ idToUpload = this._storedFileIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedFileIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(fileId) {
+ this._handler.cancel(fileId);
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedFileIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ },
+ addFiles: function(filesOrInputs) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ index, fileOrInput;
+
+ if (filesOrInputs) {
+ if (!window.FileList || !(filesOrInputs instanceof FileList)) {
+ filesOrInputs = [].concat(filesOrInputs);
+ }
+
+ for (index = 0; index < filesOrInputs.length; index+=1) {
+ fileOrInput = filesOrInputs[index];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
+ this._uploadFileList(verifiedFilesOrInputs);
+ }
+ },
+ getUuid: function(fileId) {
+ return this._handler.getUuid(fileId);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(fileId) {
+ return this._handler.getSize(fileId);
+ },
+ getFile: function(fileId) {
+ return this._handler.getFile(fileId);
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.isXhrUploadSupported(),
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, fileName, loaded, total){
+ self._onProgress(id, fileName, loaded, total);
+ self._options.callbacks.onProgress(id, fileName, loaded, total);
+ },
+ onComplete: function(id, fileName, result, xhr){
+ self._onComplete(id, fileName, result, xhr);
+ self._options.callbacks.onComplete(id, fileName, result);
+ },
+ onCancel: function(id, fileName){
+ self._onCancel(id, fileName);
+ self._options.callbacks.onCancel(id, fileName);
+ },
+ onUpload: function(id, fileName){
+ self._onUpload(id, fileName);
+ self._options.callbacks.onUpload(id, fileName);
+ },
+ onUploadChunk: function(id, fileName, chunkData){
+ self._options.callbacks.onUploadChunk(id, fileName, chunkData);
+ },
+ onResume: function(id, fileName, chunkData) {
+ return self._options.callbacks.onResume(id, fileName, chunkData);
+ },
+ onAutoRetry: function(id, fileName, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, fileName, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, fileName, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, fileName, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, fileName);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, fileName, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, fileName){
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, fileName, loaded, total){
+ },
+ _onComplete: function(id, fileName, result, xhr){
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, fileName, result, xhr);
+ },
+ _onCancel: function(id, fileName){
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedFileIndex = qq.indexOf(this._storedFileIds, id);
+ if (!this._options.autoUpload && storedFileIndex >= 0) {
+ this._storedFileIds.splice(storedFileIndex, 1);
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, fileName){},
+ _onInputChange: function(input){
+ if (qq.isXhrUploadSupported()){
+ this.addFiles(input.files);
+ } else {
+ this.addFiles(input);
+ }
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, fileName) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + fileName + "...");
+ },
+ _onAutoRetry: function(id, fileName, responseJSON) {
+ this.log("Retrying " + fileName + "...");
+ this._autoRetries[id]++;
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, fileName, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, fileName, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, fileName, "XHR returned response code " + xhr.status);
+ }
+ else {
+ var errorReason = response.error ? response.error : "Upload failure reason unknown";
+ this._options.callbacks.onError(id, fileName, errorReason);
+ }
+ }
+ },
+ _uploadFileList: function(files){
+ var validationDescriptors, index, batchInvalid;
+
+ validationDescriptors = this._getValidationDescriptors(files);
+ batchInvalid = this._options.callbacks.onValidateBatch(validationDescriptors) === false;
+
+ if (!batchInvalid) {
+ if (files.length > 0) {
+ for (index = 0; index < files.length; index++){
+ if (this._validateFile(files[index])){
+ this._uploadFile(files[index]);
+ } else {
+ if (this._options.validation.stopOnFirstInvalidFile){
+ return;
+ }
+ }
+ }
+ }
+ else {
+ this._error('noFilesError', "");
+ }
+ }
+ },
+ _uploadFile: function(fileContainer){
+ var id = this._handler.add(fileContainer);
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onSubmit(id, fileName) !== false){
+ this._onSubmit(id, fileName);
+ if (this._options.autoUpload) {
+ this._handler.upload(id);
+ }
+ else {
+ this._storeFileForLater(id);
+ }
+ }
+ },
+ _storeFileForLater: function(id) {
+ this._storedFileIds.push(id);
+ },
+ _validateFile: function(file){
+ var validationDescriptor, name, size;
+
+ validationDescriptor = this._getValidationDescriptor(file);
+ name = validationDescriptor.name;
+ size = validationDescriptor.size;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ return false;
+ }
+
+ if (!this._isAllowedExtension(name)){
+ this._error('typeError', name);
+ return false;
+
+ }
+ else if (size === 0){
+ this._error('emptyError', name);
+ return false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._error('sizeError', name);
+ return false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._error('minSizeError', name);
+ return false;
+ }
+
+ return true;
+ },
+ _error: function(code, fileName){
+ var message = this._options.messages[code];
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ var extensions = this._options.validation.allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(fileName));
+ r('{extensions}', extensions);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ this._options.callbacks.onError(null, fileName, message);
+
+ return message;
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1024;
+ i++;
+ } while (bytes > 99);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ }
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ }
+ }());
+ }
+ },
+ _parseFileName: function(file) {
+ var name;
+
+ if (file.value){
+ // it is a file input
+ // get input value and remove path to normalize
+ name = file.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+
+ return name;
+ },
+ _parseFileSize: function(file) {
+ var size;
+
+ if (!file.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (file.fileSize !== null && file.fileSize !== undefined) ? file.fileSize : file.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(file) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileName(file);
+ size = this._parseFileSize(file);
+
+ fileDescriptor.name = name;
+ if (size) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function() {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, fileId) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[fileId] = paramsCopy;
+ },
+
+ getParams: function(fileId) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (fileId != null && paramsStore[fileId]) {
+ qq.extend(paramsCopy, paramsStore[fileId]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options.request.params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function() {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, fileId) {
+ endpointStore[fileId] = endpoint;
+ },
+
+ getEndpoint: function(fileId) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (fileId != null && endpointStore[fileId]) {
+ return endpointStore[fileId];
+ }
+
+ return self._options.request.endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz, dirPending,
+ droppedFiles = [],
+ droppedEntriesCount = 0,
+ droppedEntriesParsedCount = 0,
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropArea: null,
+ extraDropzones: [],
+ hideDropzones: true,
+ multiple: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {},
+ error: function(code, filename) {},
+ log: function(message, level) {}
+ }
+ };
+
+ qq.extend(options, o);
+
+ function maybeUploadDroppedFiles() {
+ if (droppedEntriesCount === droppedEntriesParsedCount && !dirPending) {
+ options.callbacks.log('Grabbed ' + droppedFiles.length + " files after tree traversal.");
+ dz.dropDisabled(false);
+ options.callbacks.dropProcessing(false, droppedFiles);
+ }
+ }
+ function addDroppedFile(file) {
+ droppedFiles.push(file);
+ droppedEntriesParsedCount+=1;
+ maybeUploadDroppedFiles();
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i;
+
+ droppedEntriesCount+=1;
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ addDroppedFile(file);
+ });
+ }
+ else if (entry.isDirectory) {
+ dirPending = true;
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ droppedEntriesParsedCount+=1;
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]);
+ }
+
+ dirPending = false;
+
+ if (!entries.length) {
+ maybeUploadDroppedFiles();
+ }
+ });
+ }
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry;
+
+ options.callbacks.dropProcessing(true);
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.multiple) {
+ options.callbacks.dropProcessing(false);
+ options.callbacks.error('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ }
+ else {
+ droppedFiles = [];
+ droppedEntriesCount = 0;
+ droppedEntriesParsedCount = 0;
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ if (i === items.length-1) {
+ maybeUploadDroppedFiles();
+ }
+ }
+
+ else {
+ traverseFileTree(entry);
+ }
+ }
+ }
+ }
+ else {
+ options.callbacks.dropProcessing(false, dataTransfer.files);
+ dz.dropDisabled(false);
+ }
+ }
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer);
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ if (options.dropArea) {
+ options.extraDropzones.push(options.dropArea);
+ }
+
+ var i, dropzones = options.extraDropzones;
+
+ for (i=0; i < dropzones.length; i+=1){
+ setupDropzone(dropzones[i]);
+ }
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (options.dropArea && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ if (qq(options.dropArea).hasClass(options.classes.dropDisabled)) {
+ return;
+ }
+
+ options.dropArea.style.display = 'block';
+ for (i=0; i < dropzones.length; i+=1) {
+ dropzones[i].style.display = 'block';
+ }
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropzones && qq.FineUploader.prototype._leaving_document_out(e)) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropzones) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setup: function() {
+ setupDragDrop();
+ },
+
+ setupExtraDropzone: function(element) {
+ options.extraDropzones.push(element);
+ setupDropzone(element);
+ },
+
+ removeExtraDropzone: function(element) {
+ var i, dzs = options.extraDropzones;
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ dropDisabled: 'qq-upload-drop-area-disabled',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file"
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ alert(message);
+ }, 0);
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeExtraDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ cancel: function(fileId) {
+ qq.FineUploaderBasic.prototype.cancel.apply(this, arguments);
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dnd, preventSelectFiles, defaultDropAreaEl;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ defaultDropAreaEl = this._find(this._options.element, 'drop');
+ }
+
+ dnd = new qq.DragAndDrop({
+ dropArea: defaultDropAreaEl,
+ extraDropzones: this._options.dragAndDrop.extraDropzones,
+ hideDropzones: this._options.dragAndDrop.hideDropzones,
+ multiple: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {
+ var input = self._button.getInput();
+
+ if (isProcessing) {
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ }
+ else {
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+ }
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ error: function(code, filename) {
+ self._error(code, filename);
+ },
+ log: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+
+ dnd.setup();
+
+ return dnd;
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeFileForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeFileForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type){
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, fileName){
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, fileName);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, fileName, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, text, percent, cancelLink, size;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, just display final size
+ text = this._formatSize(total);
+ }
+ else {
+ // If still uploading, display percentage
+ text = this._formatProgress(loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+
+ size = this._find(item, 'size');
+ qq(size).css({display: 'inline'});
+ qq(size).setText(text);
+ },
+ _onComplete: function(id, fileName, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success){
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, fileName){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+ this._showSpinner(item);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, cancelLink, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ var item = this.getItemByFileId(id);
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(item);
+ this._showCancelLink(item);
+ return true;
+ }
+ return false;
+ },
+ _addToList: function(id, fileName){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(fileName));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) this._clearList();
+ this._listElement.appendChild(item);
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId == undefined) {
+ item = target = target.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ //TODO turn this into a real tooltip, with click trigger (so it is usable on mobile devices). See case #355 for details.
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(item) {
+ var spinnerEl = this._find(item, 'spinner');
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+ cancelLink.style.display = 'inline';
+ }
+ },
+ _error: function(code, fileName){
+ var message = qq.FineUploaderBasic.prototype._error.apply(this, arguments);
+ this._options.showMessage(message);
+ }
+});
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, dequeue, handlerImpl;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ dequeue = function(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ };
+
+ if (qq.isXhrUploadSupported()) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, log);
+ }
+
+
+ return {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ return handlerImpl.upload(id);
+ }
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id){
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ handlerImpl.cancel(id);
+ dequeue(id);
+ },
+ /**
+ * Cancels all uploads
+ */
+ cancelAll: function(){
+ qq.each(queue, function(idx, fileId) {
+ this.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ /**
+ * Returns id of files being uploaded or
+ * waiting for their turn
+ */
+ getQueue: function(){
+ return queue;
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ queue = [];
+ handlerImpl.reset();
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+};
+/*globals qq, document, setTimeout*/
+/*jslint white: true*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ api;
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHTML = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHTML);
+ //plain text response may be wrapped in tag
+ if (innerHTML && innerHTML.match(/^ ');
+ // src="javascript:false;" removes ie6 prompt on https
+
+ iframe.setAttribute('id', id);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ qq.UploadHandler.prototype.reset.apply(this, arguments);
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ options.onCancel(id, this.getName(id));
+
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ var iframe = document.getElementById(id);
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ },
+ upload: function(id){
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form = createForm(id, iframe);
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, this.getName(id));
+
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(){
+ log('iframe loaded');
+
+ var response = getIframeContentJson(iframe);
+
+ // timeout added to fix busy state in FF3.6
+ setTimeout(function(){
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ qq(iframe).remove();
+ }, 1);
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+
+ return id;
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.isFileChunkingSupported(),
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.areCookiesEnabled(),
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.end - chunkData.start;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(file, startByte, endByte) {
+ if (file.slice) {
+ return file.slice(startByte, endByte);
+ }
+ else if (file.mozSlice) {
+ return file.mozSlice(startByte, endByte);
+ }
+ else if (file.webkitSlice) {
+ return file.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ file = fileState[id].file,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(file, startBytes, endBytes)
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ fileState[id].xhr = new XMLHttpRequest();
+ return fileState[id].xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ protocol = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id);
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ params[options.inputName] = name;
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(protocol, url, true);
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ name = api.getName(id),
+ file = fileState[id].file;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", file.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedFile(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+
+ options.onComplete(id, name, response, xhr);
+ delete fileState[id].xhr;
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkData = getChunkData(id, fileState[id].remainingChunkIdxs[0]),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ persistChunkData(id, chunkData);
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ if (fileState[id].loaded < size) {
+ var totalLoaded = e.loaded + fileState[id].loaded;
+ options.onProgress(id, name, totalLoaded, size);
+ }
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.end - chunkData.start;
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ deletePersistedChunkData(id);
+ handleCompletedFile(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for file ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for file ID " + id + " - starting from the first chunk", 'error');
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedFile(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedFile(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid + cookieItemDelimiter + chunkData.part,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ var cookieName = getChunkDataCookieName(id);
+
+ qq.deleteCookie(cookieName);
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ delimiterIndex, uuid, partIndex;
+
+ if (chunkCookieValue) {
+ delimiterIndex = chunkCookieValue.indexOf(cookieItemDelimiter);
+ uuid = chunkCookieValue.substr(0, delimiterIndex);
+ partIndex = parseInt(chunkCookieValue.substr(delimiterIndex + 1, chunkCookieValue.length - delimiterIndex), 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex
+ };
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var name = api.getName(id),
+ firstChunkIndex = 0,
+ persistedChunkInfoForResume, firstChunkDataForResume, currentChunkIndex;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part);
+ if (options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume)) !== false) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].uuid = persistedChunkInfoForResume.uuid;
+ fileState[id].loaded = firstChunkDataForResume.start;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+ }
+ }
+ }
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function handleStandardFileUpload(id) {
+ var file = fileState[id].file,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, file, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+
+ api = {
+ /**
+ * Adds file to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(file){
+ if (!(file instanceof File)){
+ throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
+ }
+
+
+ var id = fileState.push({file: file}) - 1;
+ fileState[id].uuid = qq.getUniqueId();
+
+ return id;
+ },
+ getName: function(id){
+ var file = fileState[id].file;
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var file = fileState[id].file;
+ return file.fileSize != null ? file.fileSize : file.size;
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file;
+ }
+ },
+ /**
+ * Returns uploaded bytes for file identified by id
+ */
+ getLoaded: function(id){
+ return fileState[id].loaded || 0;
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry){
+ var name = this.getName(id);
+
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ },
+ cancel: function(id){
+ options.onCancel(id, this.getName(id));
+
+ if (fileState[id].xhr){
+ fileState[id].xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
diff --git a/ajax/libs/file-uploader/3.2.0/fineuploader.min.css b/ajax/libs/file-uploader/3.2.0/fineuploader.min.css
new file mode 100644
index 000000000..9de0e4eba
--- /dev/null
+++ b/ajax/libs/file-uploader/3.2.0/fineuploader.min.css
@@ -0,0 +1 @@
+.qq-uploader{position:relative;width:100%;}.qq-upload-button{display:block;width:105px;padding:7px 0;text-align:center;background:#800;border-bottom:1px solid #DDD;color:#FFF;}.qq-upload-button-hover{background:#C00;}.qq-upload-button-focus{outline:1px dotted #000;}.qq-upload-drop-area,.qq-upload-extra-drop-area{position:absolute;top:0;left:0;width:100%;height:100%;min-height:30px;z-index:2;background:#FF9797;text-align:center;}.qq-upload-drop-area span{display:block;position:absolute;top:50%;width:100%;margin-top:-8px;font-size:16px;}.qq-upload-extra-drop-area{position:relative;margin-top:50px;font-size:16px;padding-top:30px;height:20px;min-height:40px;}.qq-upload-drop-area-active{background:#FF7171;}.qq-upload-list{margin:0;padding:0;list-style:none;}.qq-upload-list li{margin:0;padding:9px;line-height:15px;font-size:16px;background-color:#FFF0BD;}.qq-upload-file,.qq-upload-spinner,.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-failed-text,.qq-upload-finished{margin-right:12px;}.qq-upload-spinner{display:inline-block;background:url("loading.gif");width:15px;height:15px;vertical-align:text-bottom;}.qq-drop-processing{display:none;}.qq-drop-processing-spinner{display:inline-block;background:url("processing.gif");width:24px;height:24px;vertical-align:text-bottom;}.qq-upload-finished{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-retry{display:none;color:#000;}.qq-upload-cancel{color:#000;}.qq-upload-retryable .qq-upload-retry{display:inline;}.qq-upload-size,.qq-upload-cancel,.qq-upload-retry{font-size:12px;font-weight:normal;}.qq-upload-failed-text{display:none;font-style:italic;font-weight:bold;}.qq-upload-failed-icon{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-fail .qq-upload-failed-text{display:inline;}.qq-upload-retrying .qq-upload-failed-text{display:inline;color:#D60000;}.qq-upload-list li.qq-upload-success{background-color:#5DA30C;color:#FFF;}.qq-upload-list li.qq-upload-fail{background-color:#D60000;color:#FFF;}.qq-progress-bar{background:-moz-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,rgba(30,87,153,1)),color-stop(50%,rgba(41,137,216,1)),color-stop(51%,rgba(32,124,202,1)),color-stop(100%,rgba(125,185,232,1)));background:-webkit-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-o-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-ms-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:linear-gradient(to bottom,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);width:0;height:15px;border-radius:6px;margin-bottom:3px;display:none;}
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.2.0/fineuploader.min.js b/ajax/libs/file-uploader/3.2.0/fineuploader.min.js
new file mode 100644
index 000000000..726bcb43b
--- /dev/null
+++ b/ajax/libs/file-uploader/3.2.0/fineuploader.min.js
@@ -0,0 +1,13 @@
+/**
+ * http://github.com/Valums-File-Uploader/file-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Original version: 1.0 © 2010 Andrew Valums ( andrew(at)valums.com )
+ * Current Maintainer (2.0+): © 2012, Ray Nicholus ( fineuploader(at)garstasio.com )
+ *
+ * Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!==null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable!==null&&variable&&typeof variable==="object"&&variable.constructor===Object};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isFileOrInput=function(maybeFileOrInput){"use strict";if(window.File&&maybeFileOrInput instanceof File){return true}else if(window.HTMLInputElement){if(maybeFileOrInput instanceof HTMLInputElement){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}else if(maybeFileOrInput.tagName){if(maybeFileOrInput.tagName.toLowerCase()==="input"){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}return false};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice||File.prototype.webkitSlice||File.prototype.mozSlice)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}})};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileName=fileName.slice(0,19)+"..."+fileName.slice(-14)}return fileName},text:{sizeSymbols:["kB","MB","GB","TB","PB","EB"]}};qq.extend(this._options,o,true);this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedFileIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._paramsStore=this._createParamsStore();this._endpointStore=this._createEndpointStore();this._handler=this._createUploadHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader] "+str,level)}},setParams:function(params,fileId){if(fileId==null){this._options.request.params=params}else{this._paramsStore.setParams(params,fileId)}},setEndpoint:function(endpoint,fileId){if(fileId==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,fileId)}},getInProgress:function(){return this._filesInProgress.length},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedFileIds.length){idToUpload=this._storedFileIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedFileIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._handler.retry(id);return true}else{return false}},cancel:function(fileId){this._handler.cancel(fileId)},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedFileIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset()},addFiles:function(filesOrInputs){var self=this,verifiedFilesOrInputs=[],index,fileOrInput;if(filesOrInputs){if(!window.FileList||!(filesOrInputs instanceof FileList)){filesOrInputs=[].concat(filesOrInputs)}for(index=0;index=0){this._storedFileIds.splice(storedFileIndex,1)}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,fileName){},_onInputChange:function(input){if(qq.isXhrUploadSupported()){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,fileName){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+fileName+"...")},_onAutoRetry:function(id,fileName,responseJSON){this.log("Retrying "+fileName+"...");this._autoRetries[id]++;this._handler.retry(id)},_shouldAutoRetry:function(id,fileName,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0){for(index=0;indexthis._options.validation.sizeLimit){this._error("sizeError",name);return false}else if(size&&size99);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileName:function(file){var name;if(file.value){name=file.value.replace(/.*(\/|\\)/,"")}else{name=file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}return name},_parseFileSize:function(file){var size;if(!file.value){size=file.fileSize!==null&&file.fileSize!==undefined?file.fileSize:file.size}return size},_getValidationDescriptor:function(file){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileName(file);size=this._parseFileSize(file);fileDescriptor.name=name;if(size){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(){var paramsStore={},self=this;return{setParams:function(params,fileId){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[fileId]=paramsCopy},getParams:function(fileId){var paramsCopy={};if(fileId!=null&¶msStore[fileId]){qq.extend(paramsCopy,paramsStore[fileId])}else{qq.extend(paramsCopy,self._options.request.params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(){var endpointStore={},self=this;return{setEndpoint:function(endpoint,fileId){endpointStore[fileId]=endpoint},getEndpoint:function(fileId){if(fileId!=null&&endpointStore[fileId]){return endpointStore[fileId]}return self._options.request.endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,dirPending,droppedFiles=[],droppedEntriesCount=0,droppedEntriesParsedCount=0,disposeSupport=new qq.DisposeSupport;options={dropArea:null,extraDropzones:[],hideDropzones:true,multiple:true,classes:{dropActive:null},callbacks:{dropProcessing:function(isProcessing,files){},error:function(code,filename){},log:function(message,level){}}};qq.extend(options,o);function maybeUploadDroppedFiles(){if(droppedEntriesCount===droppedEntriesParsedCount&&!dirPending){options.callbacks.log("Grabbed "+droppedFiles.length+" files after tree traversal.");dz.dropDisabled(false);options.callbacks.dropProcessing(false,droppedFiles)}}function addDroppedFile(file){droppedFiles.push(file);droppedEntriesParsedCount+=1;maybeUploadDroppedFiles()}function traverseFileTree(entry){var dirReader,i;droppedEntriesCount+=1;if(entry.isFile){entry.file(function(file){addDroppedFile(file)})}else if(entry.isDirectory){dirPending=true;dirReader=entry.createReader();dirReader.readEntries(function(entries){droppedEntriesParsedCount+=1;for(i=0;i1&&!options.multiple){options.callbacks.dropProcessing(false);options.callbacks.error("tooManyFilesError","");dz.dropDisabled(false)}else{droppedFiles=[];droppedEntriesCount=0;droppedEntriesParsedCount=0;if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",dropDisabled:"qq-upload-drop-area-disabled",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file"},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},showMessage:function(message){setTimeout(function(){alert(message)
+},0)}},true);qq.extend(this._options,o,true);this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop()};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeExtraDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},cancel:function(fileId){qq.FineUploaderBasic.prototype.cancel.apply(this,arguments);var item=this.getItemByFileId(fileId);qq(item).remove()},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dnd,preventSelectFiles,defaultDropAreaEl;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){defaultDropAreaEl=this._find(this._options.element,"drop")}dnd=new qq.DragAndDrop({dropArea:defaultDropAreaEl,extraDropzones:this._options.dragAndDrop.extraDropzones,hideDropzones:this._options.dragAndDrop.hideDropzones,multiple:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{dropProcessing:function(isProcessing,files){var input=self._button.getInput();if(isProcessing){qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)}else{qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles)}if(files){self.addFiles(files)}},error:function(code,filename){self._error(code,filename)},log:function(message,level){self.log(message,level)}}});dnd.setup();return dnd},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeFileForLater:function(id){qq.FineUploaderBasic.prototype._storeFileForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,fileName){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,fileName)},_onProgress:function(id,fileName,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,text,percent,cancelLink,size;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);text=this._formatSize(total)}else{text=this._formatProgress(loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"});size=this._find(item,"size");qq(size).css({display:"inline"});qq(size).setText(text)},_onComplete:function(id,fileName,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,fileName){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);var item=this.getItemByFileId(id);this._showSpinner(item)},_onBeforeAutoRetry:function(id){var item,progressBar,cancelLink,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){var item=this.getItemByFileId(id);this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(item);this._showCancelLink(item);return true}return false},_addToList:function(id,fileName){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(fileName));qq(this._find(item,"size")).hide();if(!this._options.multiple)this._clearList();this._listElement.appendChild(item)},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId==undefined){item=target=target.parentNode}if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(item){var spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");cancelLink.style.display="inline"}},_error:function(code,fileName){var message=qq.FineUploaderBasic.prototype._error.apply(this,arguments);this._options.showMessage(message)}});qq.UploadHandler=function(o){"use strict";var queue=[],options,log,dequeue,handlerImpl;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){}};qq.extend(options,o);log=options.log;dequeue=function(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){log("Cancelling "+id);options.paramsStore.remove(id);handlerImpl.cancel(id);dequeue(id)},cancelAll:function(){qq.each(queue,function(idx,fileId){this.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},getQueue:function(){return queue},reset:function(){log("Resetting upload handler");queue=[];handlerImpl.reset()},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}}};qq.UploadHandlerForm=function(o,uploadCompleteCallback,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},uploadComplete=uploadCompleteCallback,log=logCallback,api;function attachLoadEvent(iframe,callback){detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}function getIframeContentJson(iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHTML=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHTML);if(innerHTML&&innerHTML.match(/^ ');iframe.setAttribute("id",id);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){return inputs[id].value.replace(/.*(\/|\\)/,"")},isValid:function(id){return inputs[id]!==undefined},reset:function(){qq.UploadHandler.prototype.reset.apply(this,arguments);inputs=[];uuids=[];detachLoadEvents={}},getUuid:function(id){return uuids[id]},cancel:function(id){options.onCancel(id,this.getName(id));delete inputs[id];delete uuids[id];delete detachLoadEvents[id];var iframe=document.getElementById(id);if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form=createForm(id,iframe);if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,this.getName(id));form.appendChild(input);attachLoadEvent(iframe,function(){log("iframe loaded");var response=getIframeContentJson(iframe);setTimeout(function(){detachLoadEvents[id]();delete detachLoadEvents[id];qq(iframe).remove()},1);if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove();return id}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,logCallback){"use strict";var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.isFileChunkingSupported(),resumeEnabled=options.resume.enabled&&chunkFiles&&qq.areCookiesEnabled(),resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.end-chunkData.start;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(file,startByte,endByte){if(file.slice){return file.slice(startByte,endByte)}else if(file.mozSlice){return file.mozSlice(startByte,endByte)}else if(file.webkitSlice){return file.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),file=fileState[id].file,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(file,startBytes,endBytes)}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){fileState[id].xhr=new XMLHttpRequest;return fileState[id].xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,protocol=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id);params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size}if(!options.paramsInBody){params[options.inputName]=name;url=qq.obj2url(params,endpoint)}xhr.open(protocol,url,true);if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,name=api.getName(id),file=fileState[id].file;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",file.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedFile(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);delete fileState[id].xhr;uploadComplete(id)}function uploadNextChunk(id){var chunkData=getChunkData(id,fileState[id].remainingChunkIdxs[0]),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}persistChunkData(id,chunkData);xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){if(fileState[id].loaded0){uploadNextChunk(id)}else{deletePersistedChunkData(id);handleCompletedFile(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(xhr){var response;try{response=qq.parseJson(xhr.responseText)}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for file ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id)}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for file ID "+id+" - starting from the first chunk","error");api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedFile(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedFile(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),delimiterIndex,uuid,partIndex;if(chunkCookieValue){delimiterIndex=chunkCookieValue.indexOf(cookieItemDelimiter);uuid=chunkCookieValue.substr(0,delimiterIndex);partIndex=parseInt(chunkCookieValue.substr(delimiterIndex+1,chunkCookieValue.length-delimiterIndex),10);return{uuid:uuid,part:partIndex}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function handleFileChunkingUpload(id,retry){var name=api.getName(id),firstChunkIndex=0,persistedChunkInfoForResume,firstChunkDataForResume,currentChunkIndex;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part);if(options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume))!==false){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].uuid=persistedChunkInfoForResume.uuid;fileState[id].loaded=firstChunkDataForResume.start;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex)}}}for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}}uploadNextChunk(id)}function handleStandardFileUpload(id){var file=fileState[id].file,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,file,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}api={add:function(file){if(!(file instanceof File)){throw new Error("Passed obj in not a File (in qq.UploadHandlerXhr)")}var id=fileState.push({file:file})-1;fileState[id].uuid=qq.getUniqueId();return id},getName:function(id){var file=fileState[id].file;return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name},getSize:function(id){var file=fileState[id].file;return file.fileSize!=null?file.fileSize:file.size},getFile:function(id){if(fileState[id]){return fileState[id].file}},getLoaded:function(id){return fileState[id].loaded||0},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}},cancel:function(id){options.onCancel(id,this.getName(id));if(fileState[id].xhr){fileState[id].xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.2.0/loading.gif b/ajax/libs/file-uploader/3.2.0/loading.gif
new file mode 100644
index 000000000..6fba77609
Binary files /dev/null and b/ajax/libs/file-uploader/3.2.0/loading.gif differ
diff --git a/ajax/libs/file-uploader/3.2.0/processing.gif b/ajax/libs/file-uploader/3.2.0/processing.gif
new file mode 100644
index 000000000..7c99504e1
Binary files /dev/null and b/ajax/libs/file-uploader/3.2.0/processing.gif differ
diff --git a/ajax/libs/file-uploader/3.3.0/fineuploader-jquery.js b/ajax/libs/file-uploader/3.3.0/fineuploader-jquery.js
new file mode 100644
index 000000000..98f2f2df2
--- /dev/null
+++ b/ajax/libs/file-uploader/3.3.0/fineuploader-jquery.js
@@ -0,0 +1,3929 @@
+/**
+ * http://github.com/Valums-File-Uploader/file-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Original version: 1.0 © 2010 Andrew Valums ( andrew(at)valums.com )
+ * Current Maintainer (2.0+): © 2012, Ray Nicholus ( fineuploader(at)garstasio.com )
+ *
+ * Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity !== null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+ if (qq.isBlob(maybeFileOrInput) && window.File && maybeFileOrInput instanceof File) {
+ return true;
+ }
+ else if (window.HTMLInputElement) {
+ if (maybeFileOrInput instanceof HTMLInputElement) {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ else if (maybeFileOrInput.tagName) {
+ if (maybeFileOrInput.tagName.toLowerCase() === 'input') {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && maybeBlob instanceof Blob;
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice || File.prototype.webkitSlice || File.prototype.mozSlice);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(obj, callback) {
+ "use strict";
+ var key, retVal;
+ if (obj) {
+ for (key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ retVal = callback(key, obj[key]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ c;
+
+ for(var i=0;i < ca.length;i++) {
+ c = ca[i];
+ while (c.charAt(0)==' ') {
+ c = c.substring(1,c.length);
+ }
+ if (c.indexOf(nameEQ) === 0) {
+ return c.substring(nameEQ.length,c.length);
+ }
+ }
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+qq.UploadButton = function(o){
+ this._options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input){},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ qq.extend(this._options, o);
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._element = this._options.element;
+
+ // make button suitable container for input
+ qq(this._element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ this._input = this._createInput();
+};
+
+qq.UploadButton.prototype = {
+ /* returns file input element */
+ getInput: function(){
+ return this._input;
+ },
+ /* cleans/recreates the file input */
+ reset: function(){
+ if (this._input.parentNode){
+ qq(this._input).remove();
+ }
+
+ qq(this._element).removeClass(this._options.focusClass);
+ this._input = this._createInput();
+ },
+ _createInput: function(){
+ var input = document.createElement("input");
+
+ if (this._options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (this._options.acceptFiles) input.setAttribute("accept", this._options.acceptFiles);
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", this._options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ this._element.appendChild(input);
+
+ var self = this;
+ this._disposeSupport.attach(input, 'change', function(){
+ self._options.onChange(input);
+ });
+
+ this._disposeSupport.attach(input, 'mouseover', function(){
+ qq(self._element).addClass(self._options.hoverClass);
+ });
+ this._disposeSupport.attach(input, 'mouseout', function(){
+ qq(self._element).removeClass(self._options.hoverClass);
+ });
+ this._disposeSupport.attach(input, 'focus', function(){
+ qq(self._element).addClass(self._options.focusClass);
+ });
+ this._disposeSupport.attach(input, 'blur', function(){
+ qq(self._element).removeClass(self._options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+};
+qq.FineUploaderBasic = function(o){
+ var that = this;
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ stopOnFirstInvalidFile: true
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onComplete: function(id, name, responseJSON){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){}
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'Misc data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ }
+ };
+
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ // number of files being uploaded
+ this._filesInProgress = [];
+
+ this._storedIds = [];
+
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function(){
+ return this._filesInProgress.length;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ },
+ addFiles: function(filesBlobDataOrInputs) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ index, fileOrInput;
+
+ if (filesBlobDataOrInputs) {
+ if (!window.FileList || !(filesBlobDataOrInputs instanceof FileList)) {
+ filesBlobDataOrInputs = [].concat(filesBlobDataOrInputs);
+ }
+
+ for (index = 0; index < filesBlobDataOrInputs.length; index+=1) {
+ fileOrInput = filesBlobDataOrInputs[index];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
+ this._uploadFileOrBlobDataList(verifiedFilesOrInputs);
+ }
+ },
+ addBlobs: function(blobDataOrArray) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._uploadFileOrBlobDataList(verifiedBlobDataList);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.isXhrUploadSupported(),
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result);
+ },
+ onCancel: function(id, name){
+ self._onCancel(id, name);
+ self._options.callbacks.onCancel(id, name);
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name){
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total){
+ },
+ _onComplete: function(id, name, result, xhr){
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name){
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected ||
+ (this._options.cors.expected && (qq.ie10() || !qq.ie()))
+ )
+ );
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id)) {
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ }
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(fileId) {},
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status);
+ }
+ else {
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name){},
+ _onInputChange: function(input){
+ if (qq.isXhrUploadSupported()){
+ this.addFiles(input.files);
+ } else {
+ this.addFiles(input);
+ }
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status);
+ }
+ else {
+ var errorReason = response.error ? response.error : "Upload failure reason unknown";
+ this._options.callbacks.onError(id, name, errorReason);
+ }
+ }
+ },
+ _uploadFileOrBlobDataList: function(fileOrBlobDataList){
+ var validationDescriptors, index, batchInvalid;
+
+ validationDescriptors = this._getValidationDescriptors(fileOrBlobDataList);
+ batchInvalid = this._options.callbacks.onValidateBatch(validationDescriptors) === false;
+
+ if (!batchInvalid) {
+ if (fileOrBlobDataList.length > 0) {
+ for (index = 0; index < fileOrBlobDataList.length; index++){
+ if (this._validateFileOrBlobData(fileOrBlobDataList[index])){
+ this._upload(fileOrBlobDataList[index]);
+ } else {
+ if (this._options.validation.stopOnFirstInvalidFile){
+ return;
+ }
+ }
+ }
+ }
+ else {
+ this._error('noFilesError', "");
+ }
+ }
+ },
+ _upload: function(blobOrFileContainer){
+ var id = this._handler.add(blobOrFileContainer);
+ var name = this._handler.getName(id);
+
+ if (this._options.callbacks.onSubmit(id, name) !== false){
+ this._onSubmit(id, name);
+ if (this._options.autoUpload) {
+ this._handler.upload(id);
+ }
+ else {
+ this._storeForLater(id);
+ }
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _validateFileOrBlobData: function(fileOrBlobData){
+ var validationDescriptor, name, size;
+
+ validationDescriptor = this._getValidationDescriptor(fileOrBlobData);
+ name = validationDescriptor.name;
+ size = validationDescriptor.size;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ return false;
+ }
+
+ if (qq.isFileOrInput(fileOrBlobData) && !this._isAllowedExtension(name)){
+ this._error('typeError', name);
+ return false;
+
+ }
+ else if (size === 0){
+ this._error('emptyError', name);
+ return false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._error('sizeError', name);
+ return false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._error('minSizeError', name);
+ return false;
+ }
+
+ return true;
+ },
+ _error: function(code, name){
+ var message = this._options.messages[code];
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ var extensions = this._options.validation.allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensions);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ this._options.callbacks.onError(null, name, message);
+
+ return message;
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1024;
+ i++;
+ } while (bytes > 99);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ }
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ }
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz, dirPending,
+ droppedFiles = [],
+ droppedEntriesCount = 0,
+ droppedEntriesParsedCount = 0,
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropArea: null,
+ extraDropzones: [],
+ hideDropzones: true,
+ multiple: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {},
+ error: function(code, filename) {},
+ log: function(message, level) {}
+ }
+ };
+
+ qq.extend(options, o);
+
+ function maybeUploadDroppedFiles() {
+ if (droppedEntriesCount === droppedEntriesParsedCount && !dirPending) {
+ options.callbacks.log('Grabbed ' + droppedFiles.length + " files after tree traversal.");
+ dz.dropDisabled(false);
+ options.callbacks.dropProcessing(false, droppedFiles);
+ }
+ }
+ function addDroppedFile(file) {
+ droppedFiles.push(file);
+ droppedEntriesParsedCount+=1;
+ maybeUploadDroppedFiles();
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i;
+
+ droppedEntriesCount+=1;
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ addDroppedFile(file);
+ });
+ }
+ else if (entry.isDirectory) {
+ dirPending = true;
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ droppedEntriesParsedCount+=1;
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]);
+ }
+
+ dirPending = false;
+
+ if (!entries.length) {
+ maybeUploadDroppedFiles();
+ }
+ });
+ }
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry;
+
+ options.callbacks.dropProcessing(true);
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.multiple) {
+ options.callbacks.dropProcessing(false);
+ options.callbacks.error('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ }
+ else {
+ droppedFiles = [];
+ droppedEntriesCount = 0;
+ droppedEntriesParsedCount = 0;
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ if (i === items.length-1) {
+ maybeUploadDroppedFiles();
+ }
+ }
+
+ else {
+ traverseFileTree(entry);
+ }
+ }
+ }
+ }
+ else {
+ options.callbacks.dropProcessing(false, dataTransfer.files);
+ dz.dropDisabled(false);
+ }
+ }
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer);
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ if (options.dropArea) {
+ options.extraDropzones.push(options.dropArea);
+ }
+
+ var i, dropzones = options.extraDropzones;
+
+ for (i=0; i < dropzones.length; i+=1){
+ setupDropzone(dropzones[i]);
+ }
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (options.dropArea && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ if (qq(options.dropArea).hasClass(options.classes.dropDisabled)) {
+ return;
+ }
+
+ options.dropArea.style.display = 'block';
+ for (i=0; i < dropzones.length; i+=1) {
+ dropzones[i].style.display = 'block';
+ }
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropzones && qq.FineUploader.prototype._leaving_document_out(e)) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropzones) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setup: function() {
+ setupDragDrop();
+ },
+
+ setupExtraDropzone: function(element) {
+ options.extraDropzones.push(element);
+ setupDropzone(element);
+ },
+
+ removeExtraDropzone: function(element) {
+ var i, dzs = options.extraDropzones;
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ dropDisabled: 'qq-upload-drop-area-disabled',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file"
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeExtraDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dnd, preventSelectFiles, defaultDropAreaEl;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ defaultDropAreaEl = this._find(this._options.element, 'drop');
+ }
+
+ dnd = new qq.DragAndDrop({
+ dropArea: defaultDropAreaEl,
+ extraDropzones: this._options.dragAndDrop.extraDropzones,
+ hideDropzones: this._options.dragAndDrop.hideDropzones,
+ multiple: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {
+ var input = self._button.getInput();
+
+ if (isProcessing) {
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ }
+ else {
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+ }
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ error: function(code, filename) {
+ self._error(code, filename);
+ },
+ log: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+
+ dnd.setup();
+
+ return dnd;
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type){
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name){
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ var item = this.getItemByFileId(id);
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ return false;
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id) !== false) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ }
+ }
+ else {
+ this.log("Delete request ignored for file ID " + id + ", delete feature is disabled.", "warn");
+ return false;
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ this._listElement.appendChild(item);
+
+ if (this._options.display.fileSizeOnSubmit && qq.isXhrUploadSupported()) {
+ this._displayFileSize(id);
+ }
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = target = target.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _error: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._error.apply(this, arguments);
+ this._options.showMessage(message);
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, dequeue, handlerImpl;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ dequeue = function(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.isXhrUploadSupported()) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, log);
+ }
+
+
+ return {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ return handlerImpl.upload(id);
+ }
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ handlerImpl.cancel(id);
+ dequeue(id);
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ /**
+ * Returns id of files being uploaded or
+ * waiting for their turn
+ */
+ getQueue: function(){
+ return queue;
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ queue = [];
+ handlerImpl.reset();
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var id = iframe.id;
+
+ onloadCallbacks[uuids[id]] = callback;
+
+ detachLoadEvents[id] = qq(iframe).attach('load', function() {
+ if (inputs[id]) {
+ log("Received iframe load event for CORS upload request (file id " + id + ")");
+
+ postMessageCallbackTimers[id] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for file id " + id;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(id, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = qq.parseJson(message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+
+ detachLoadEvent(id);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(id);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHTML = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHTML);
+ //plain text response may be wrapped in tag
+ if (innerHTML && innerHTML.match(/^ ');
+
+ iframe.setAttribute('id', id);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ qq.UploadHandler.prototype.reset.apply(this, arguments);
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ options.onCancel(id, this.getName(id));
+
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(id);
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ },
+ upload: function(id){
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, this.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+
+ return id;
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.isFileChunkingSupported(),
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.areCookiesEnabled(),
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+
+ options.onComplete(id, name, response, xhr);
+ delete fileState[id].xhr;
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var name = api.getName(id),
+ firstChunkIndex = 0,
+ persistedChunkInfoForResume, firstChunkDataForResume, currentChunkIndex;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part);
+ if (options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume)) !== false) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].uuid = persistedChunkInfoForResume.uuid;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+ }
+ }
+ }
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id;
+
+ if (fileOrBlobData instanceof File) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (fileOrBlobData.blob instanceof Blob) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ fileState[id].uuid = qq.getUniqueId();
+ return id;
+ },
+ getName: function(id){
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ /**
+ * Returns uploaded bytes for file identified by id
+ */
+ getLoaded: function(id){
+ return fileState[id].loaded || 0;
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry){
+ var name = this.getName(id);
+
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ },
+ cancel: function(id){
+ var xhr = fileState[id].xhr;
+
+ options.onCancel(id, this.getName(id));
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var uploader, $el, init, dataStore, pluginOption, pluginOptions, addCallbacks, transformVariables, isValidCommand,
+ delegateCommand;
+
+ pluginOptions = ['uploaderType'];
+
+ init = function (options) {
+ if (options) {
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+
+ if (pluginOption('uploaderType') === 'basic') {
+ uploader(new qq.FineUploaderBasic(xformedOpts));
+ }
+ else {
+ uploader(new qq.FineUploader(xformedOpts));
+ }
+ }
+
+ return $el;
+ };
+
+ dataStore = function(key, val) {
+ var data = $el.data('fineuploader');
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data('fineuploader', data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ //the underlying Fine Uploader instance is stored in jQuery's data stored, associated with the element
+ // tied to this instance of the plug-in
+ uploader = function(instanceToStore) {
+ return dataStore('uploader', instanceToStore);
+ };
+
+ pluginOption = function(option, optionVal) {
+ return dataStore(option, optionVal);
+ };
+
+ //implement all callbacks defined in Fine Uploader as functions that trigger appropriately names events and
+ // return the result of executing the bound handler back to Fine Uploader
+ addCallbacks = function(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {};
+
+ $.each(new qq.FineUploaderBasic()._options.callbacks, function(prop, func) {
+ var name, $callbackEl;
+
+ name = /^on(\w+)/.exec(prop)[1];
+ name = name.substring(0, 1).toLowerCase() + name.substring(1);
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var args = Array.prototype.slice.call(arguments);
+ return $callbackEl.triggerHandler(name, args);
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ transformVariables = function(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ if (source.uploaderType !== 'basic') {
+ xformed = { element : $el[0] };
+ }
+ else {
+ xformed = {};
+ }
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if ($.inArray(prop, pluginOptions) >= 0) {
+ pluginOption(prop, val);
+ }
+ else if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ isValidCommand = function(command) {
+ return $.type(command) === "string" &&
+ !command.match(/^_/) && //enforce private methods convention
+ uploader()[command] !== undefined;
+ };
+
+ //assuming we have already verified that this is a valid command, call the associated function in the underlying
+ // Fine Uploader instance (passing along the arguments from the caller) and return the result of the call back to the caller
+ delegateCommand = function(command) {
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
+
+ transformVariables(origArgs, xformedArgs);
+
+ return uploader()[command].apply(uploader(), xformedArgs);
+ };
+
+ $.fn.fineUploader = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (uploader() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error('Method ' + optionsOrCommand + ' does not exist on jQuery.fineUploader');
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
diff --git a/ajax/libs/file-uploader/3.3.0/fineuploader-jquery.min.js b/ajax/libs/file-uploader/3.3.0/fineuploader-jquery.min.js
new file mode 100644
index 000000000..95e660013
--- /dev/null
+++ b/ajax/libs/file-uploader/3.3.0/fineuploader-jquery.min.js
@@ -0,0 +1,14 @@
+/**
+ * http://github.com/Valums-File-Uploader/file-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Original version: 1.0 © 2010 Andrew Valums ( andrew(at)valums.com )
+ * Current Maintainer (2.0+): © 2012, Ray Nicholus ( fineuploader(at)garstasio.com )
+ *
+ * Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!==null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable!==null&&variable&&typeof variable==="object"&&variable.constructor===Object};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFileOrInput=function(maybeFileOrInput){"use strict";if(qq.isBlob(maybeFileOrInput)&&window.File&&maybeFileOrInput instanceof File){return true}else if(window.HTMLInputElement){if(maybeFileOrInput instanceof HTMLInputElement){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}else if(maybeFileOrInput.tagName){if(maybeFileOrInput.tagName.toLowerCase()==="input"){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&maybeBlob instanceof Blob};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice||File.prototype.webkitSlice||File.prototype.mozSlice)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}})};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"Misc data",paramNames:{name:"qqblobname"}}};qq.extend(this._options,o,true);this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset()},addFiles:function(filesBlobDataOrInputs){var self=this,verifiedFilesOrInputs=[],index,fileOrInput;if(filesBlobDataOrInputs){if(!window.FileList||!(filesBlobDataOrInputs instanceof FileList)){filesBlobDataOrInputs=[].concat(filesBlobDataOrInputs)}for(index=0;index=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||this._options.cors.expected&&(qq.ie10()||!qq.ie()))},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)){this._deleteHandler.sendDelete(id,this.getUuid(id))}}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(fileId){},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);if(isError){this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status)}else{this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){},_onInputChange:function(input){if(qq.isXhrUploadSupported()){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0){for(index=0;indexthis._options.validation.sizeLimit){this._error("sizeError",name);return false}else if(size&&size99);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,dirPending,droppedFiles=[],droppedEntriesCount=0,droppedEntriesParsedCount=0,disposeSupport=new qq.DisposeSupport;options={dropArea:null,extraDropzones:[],hideDropzones:true,multiple:true,classes:{dropActive:null},callbacks:{dropProcessing:function(isProcessing,files){},error:function(code,filename){},log:function(message,level){}}};qq.extend(options,o);function maybeUploadDroppedFiles(){if(droppedEntriesCount===droppedEntriesParsedCount&&!dirPending){options.callbacks.log("Grabbed "+droppedFiles.length+" files after tree traversal.");dz.dropDisabled(false);options.callbacks.dropProcessing(false,droppedFiles)}}function addDroppedFile(file){droppedFiles.push(file);droppedEntriesParsedCount+=1;maybeUploadDroppedFiles()}function traverseFileTree(entry){var dirReader,i;droppedEntriesCount+=1;if(entry.isFile){entry.file(function(file){addDroppedFile(file)})}else if(entry.isDirectory){dirPending=true;dirReader=entry.createReader();dirReader.readEntries(function(entries){droppedEntriesParsedCount+=1;for(i=0;i1&&!options.multiple){options.callbacks.dropProcessing(false);options.callbacks.error("tooManyFilesError","");dz.dropDisabled(false)}else{droppedFiles=[];droppedEntriesCount=0;droppedEntriesParsedCount=0;if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",dropDisabled:"qq-upload-drop-area-disabled",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file"},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false},showMessage:function(message){setTimeout(function(){alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)}},true);qq.extend(this._options,o,true);this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop()};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeExtraDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop()},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dnd,preventSelectFiles,defaultDropAreaEl;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){defaultDropAreaEl=this._find(this._options.element,"drop")}dnd=new qq.DragAndDrop({dropArea:defaultDropAreaEl,extraDropzones:this._options.dragAndDrop.extraDropzones,hideDropzones:this._options.dragAndDrop.hideDropzones,multiple:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{dropProcessing:function(isProcessing,files){var input=self._button.getInput();if(isProcessing){qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)}else{qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles)}if(files){self.addFiles(files)}},error:function(code,filename){self._error(code,filename)},log:function(message,level){self.log(message,level)}}});dnd.setup();return dnd},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){var item=this.getItemByFileId(id);this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}return false},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)!==false){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}}}else{this.log("Delete request ignored for file ID "+id+", delete feature is disabled.","warn");return false}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}this._listElement.appendChild(item);if(this._options.display.fileSizeOnSubmit&&qq.isXhrUploadSupported()){this._displayFileSize(id)}},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=target=target.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_error:function(code,name){var message=qq.FineUploaderBasic.prototype._error.apply(this,arguments);this._options.showMessage(message)}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,dequeue,handlerImpl;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){}};qq.extend(options,o);log=options.log;dequeue=function(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){log("Cancelling "+id);options.paramsStore.remove(id);handlerImpl.cancel(id);dequeue(id)},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},getQueue:function(){return queue},reset:function(){log("Resetting upload handler");queue=[];handlerImpl.reset()},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}}};qq.UploadHandlerForm=function(o,uploadCompleteCallback,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var id=iframe.id;onloadCallbacks[uuids[id]]=callback;detachLoadEvents[id]=qq(iframe).attach("load",function(){if(inputs[id]){log("Received iframe load event for CORS upload request (file id "+id+")");postMessageCallbackTimers[id]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for file id "+id;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(id,function(message){log("Received the following window message: '"+message+"'");var response=qq.parseJson(message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];detachLoadEvent(id);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(id);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHTML=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHTML);if(innerHTML&&innerHTML.match(/^ ');iframe.setAttribute("id",id);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){return inputs[id].value.replace(/.*(\/|\\)/,"")},isValid:function(id){return inputs[id]!==undefined},reset:function(){qq.UploadHandler.prototype.reset.apply(this,arguments);inputs=[];uuids=[];detachLoadEvents={}},getUuid:function(id){return uuids[id]},cancel:function(id){options.onCancel(id,this.getName(id));delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(id);if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,this.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove();return id}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,logCallback){"use strict";var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.isFileChunkingSupported(),resumeEnabled=options.resume.enabled&&chunkFiles&&qq.areCookiesEnabled(),resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);delete fileState[id].xhr;uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;
+fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(xhr){var response;try{response=qq.parseJson(xhr.responseText)}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function handleFileChunkingUpload(id,retry){var name=api.getName(id),firstChunkIndex=0,persistedChunkInfoForResume,firstChunkDataForResume,currentChunkIndex;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part);if(options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume))!==false){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].uuid=persistedChunkInfoForResume.uuid;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex)}}}for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}}uploadNextChunk(id)}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}api={add:function(fileOrBlobData){var id;if(fileOrBlobData instanceof File){id=fileState.push({file:fileOrBlobData})-1}else if(fileOrBlobData.blob instanceof Blob){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}fileState[id].uuid=qq.getUniqueId();return id},getName:function(id){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},getLoaded:function(id){return fileState[id].loaded||0},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}},cancel:function(id){var xhr=fileState[id].xhr;options.onCancel(id,this.getName(id));if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};!function($){"use strict";var uploader,$el,init,dataStore,pluginOption,pluginOptions,addCallbacks,transformVariables,isValidCommand,delegateCommand;pluginOptions=["uploaderType"];init=function(options){if(options){var xformedOpts=transformVariables(options);addCallbacks(xformedOpts);if(pluginOption("uploaderType")==="basic"){uploader(new qq.FineUploaderBasic(xformedOpts))}else{uploader(new qq.FineUploader(xformedOpts))}}return $el};dataStore=function(key,val){var data=$el.data("fineuploader");if(val){if(data===undefined){data={}}data[key]=val;$el.data("fineuploader",data)}else{if(data===undefined){return null}return data[key]}};uploader=function(instanceToStore){return dataStore("uploader",instanceToStore)};pluginOption=function(option,optionVal){return dataStore(option,optionVal)};addCallbacks=function(transformedOpts){var callbacks=transformedOpts.callbacks={};$.each((new qq.FineUploaderBasic)._options.callbacks,function(prop,func){var name,$callbackEl;name=/^on(\w+)/.exec(prop)[1];name=name.substring(0,1).toLowerCase()+name.substring(1);$callbackEl=$el;callbacks[prop]=function(){var args=Array.prototype.slice.call(arguments);return $callbackEl.triggerHandler(name,args)}})};transformVariables=function(source,dest){var xformed,arrayVals;if(dest===undefined){if(source.uploaderType!=="basic"){xformed={element:$el[0]}}else{xformed={}}}else{xformed=dest}$.each(source,function(prop,val){if($.inArray(prop,pluginOptions)>=0){pluginOption(prop,val)}else if(val instanceof $){xformed[prop]=val[0]}else if($.isPlainObject(val)){xformed[prop]={};transformVariables(val,xformed[prop])}else if($.isArray(val)){arrayVals=[];$.each(val,function(idx,arrayVal){if(arrayVal instanceof $){$.merge(arrayVals,arrayVal)}else{arrayVals.push(arrayVal)}});xformed[prop]=arrayVals}else{xformed[prop]=val}});if(dest===undefined){return xformed}};isValidCommand=function(command){return $.type(command)==="string"&&!command.match(/^_/)&&uploader()[command]!==undefined};delegateCommand=function(command){var xformedArgs=[],origArgs=Array.prototype.slice.call(arguments,1);transformVariables(origArgs,xformedArgs);return uploader()[command].apply(uploader(),xformedArgs)};$.fn.fineUploader=function(optionsOrCommand){var self=this,selfArgs=arguments,retVals=[];this.each(function(index,el){$el=$(el);if(uploader()&&isValidCommand(optionsOrCommand)){retVals.push(delegateCommand.apply(self,selfArgs));if(self.length===1){return false}}else if(typeof optionsOrCommand==="object"||!optionsOrCommand){init.apply(self,selfArgs)}else{$.error("Method "+optionsOrCommand+" does not exist on jQuery.fineUploader")}});if(retVals.length===1){return retVals[0]}else if(retVals.length>1){return retVals}return this}}(jQuery);
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.3.0/fineuploader.css b/ajax/libs/file-uploader/3.3.0/fineuploader.css
new file mode 100644
index 000000000..0d073b3bb
--- /dev/null
+++ b/ajax/libs/file-uploader/3.3.0/fineuploader.css
@@ -0,0 +1,148 @@
+/*
+ * Original version: 1.0 © 2010 Andrew Valums ( andrew(at)valums.com )
+ * Current Maintainer (2.0+): 2012, Ray Nicholus ( fineuploader(at)garstasio.com )
+ *
+ * Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
+ */
+.qq-uploader {
+ position: relative;
+ width: 100%;
+}
+.qq-upload-button {
+ display: block;
+ width: 105px;
+ padding: 7px 0;
+ text-align: center;
+ background: #880000;
+ border-bottom: 1px solid #DDD;
+ color: #FFF;
+}
+.qq-upload-button-hover {
+ background: #CC0000;
+}
+.qq-upload-button-focus {
+ outline: 1px dotted #000000;
+}
+.qq-upload-drop-area, .qq-upload-extra-drop-area {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ min-height: 30px;
+ z-index: 2;
+ background: #FF9797;
+ text-align: center;
+}
+.qq-upload-drop-area span {
+ display: block;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ margin-top: -8px;
+ font-size: 16px;
+}
+.qq-upload-extra-drop-area {
+ position: relative;
+ margin-top: 50px;
+ font-size: 16px;
+ padding-top: 30px;
+ height: 20px;
+ min-height: 40px;
+}
+.qq-upload-drop-area-active {
+ background: #FF7171;
+}
+.qq-upload-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+.qq-upload-list li {
+ margin: 0;
+ padding: 9px;
+ line-height: 15px;
+ font-size: 16px;
+ background-color: #FFF0BD;
+}
+.qq-upload-file, .qq-upload-spinner, .qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text, .qq-upload-finished, .qq-upload-delete {
+ margin-right: 12px;
+}
+.qq-upload-file {
+}
+.qq-upload-spinner {
+ display: inline-block;
+ background: url("loading.gif");
+ width: 15px;
+ height: 15px;
+ vertical-align: text-bottom;
+}
+.qq-drop-processing {
+ display: none;
+}
+.qq-drop-processing-spinner {
+ display: inline-block;
+ background: url("processing.gif");
+ width: 24px;
+ height: 24px;
+ vertical-align: text-bottom;
+}
+.qq-upload-finished {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-retry, .qq-upload-delete {
+ display: none;
+ color: #000000;
+}
+.qq-upload-cancel, .qq-upload-delete {
+ color: #000000;
+}
+.qq-upload-retryable .qq-upload-retry {
+ display: inline;
+}
+.qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-delete {
+ font-size: 12px;
+ font-weight: normal;
+}
+.qq-upload-failed-text {
+ display: none;
+ font-style: italic;
+ font-weight: bold;
+}
+.qq-upload-failed-icon {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-fail .qq-upload-failed-text {
+ display: inline;
+}
+.qq-upload-retrying .qq-upload-failed-text {
+ display: inline;
+ color: #D60000;
+}
+.qq-upload-list li.qq-upload-success {
+ background-color: #5DA30C;
+ color: #FFFFFF;
+}
+.qq-upload-list li.qq-upload-fail {
+ background-color: #D60000;
+ color: #FFFFFF;
+}
+.qq-progress-bar {
+ background: -moz-linear-gradient(top, rgba(30,87,153,1) 0%, rgba(41,137,216,1) 50%, rgba(32,124,202,1) 51%, rgba(125,185,232,1) 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(30,87,153,1)), color-stop(50%,rgba(41,137,216,1)), color-stop(51%,rgba(32,124,202,1)), color-stop(100%,rgba(125,185,232,1))); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* IE10+ */
+ background: linear-gradient(to bottom, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* W3C */
+ width: 0%;
+ height: 15px;
+ border-radius: 6px;
+ margin-bottom: 3px;
+ display: none;
+}
diff --git a/ajax/libs/file-uploader/3.3.0/fineuploader.js b/ajax/libs/file-uploader/3.3.0/fineuploader.js
new file mode 100644
index 000000000..b109b8a1c
--- /dev/null
+++ b/ajax/libs/file-uploader/3.3.0/fineuploader.js
@@ -0,0 +1,3761 @@
+/**
+ * http://github.com/Valums-File-Uploader/file-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Original version: 1.0 © 2010 Andrew Valums ( andrew(at)valums.com )
+ * Current Maintainer (2.0+): © 2012, Ray Nicholus ( fineuploader(at)garstasio.com )
+ *
+ * Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity !== null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+ if (qq.isBlob(maybeFileOrInput) && window.File && maybeFileOrInput instanceof File) {
+ return true;
+ }
+ else if (window.HTMLInputElement) {
+ if (maybeFileOrInput instanceof HTMLInputElement) {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ else if (maybeFileOrInput.tagName) {
+ if (maybeFileOrInput.tagName.toLowerCase() === 'input') {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && maybeBlob instanceof Blob;
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice || File.prototype.webkitSlice || File.prototype.mozSlice);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(obj, callback) {
+ "use strict";
+ var key, retVal;
+ if (obj) {
+ for (key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ retVal = callback(key, obj[key]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ c;
+
+ for(var i=0;i < ca.length;i++) {
+ c = ca[i];
+ while (c.charAt(0)==' ') {
+ c = c.substring(1,c.length);
+ }
+ if (c.indexOf(nameEQ) === 0) {
+ return c.substring(nameEQ.length,c.length);
+ }
+ }
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+qq.UploadButton = function(o){
+ this._options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input){},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ qq.extend(this._options, o);
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._element = this._options.element;
+
+ // make button suitable container for input
+ qq(this._element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ this._input = this._createInput();
+};
+
+qq.UploadButton.prototype = {
+ /* returns file input element */
+ getInput: function(){
+ return this._input;
+ },
+ /* cleans/recreates the file input */
+ reset: function(){
+ if (this._input.parentNode){
+ qq(this._input).remove();
+ }
+
+ qq(this._element).removeClass(this._options.focusClass);
+ this._input = this._createInput();
+ },
+ _createInput: function(){
+ var input = document.createElement("input");
+
+ if (this._options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (this._options.acceptFiles) input.setAttribute("accept", this._options.acceptFiles);
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", this._options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ this._element.appendChild(input);
+
+ var self = this;
+ this._disposeSupport.attach(input, 'change', function(){
+ self._options.onChange(input);
+ });
+
+ this._disposeSupport.attach(input, 'mouseover', function(){
+ qq(self._element).addClass(self._options.hoverClass);
+ });
+ this._disposeSupport.attach(input, 'mouseout', function(){
+ qq(self._element).removeClass(self._options.hoverClass);
+ });
+ this._disposeSupport.attach(input, 'focus', function(){
+ qq(self._element).addClass(self._options.focusClass);
+ });
+ this._disposeSupport.attach(input, 'blur', function(){
+ qq(self._element).removeClass(self._options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+};
+qq.FineUploaderBasic = function(o){
+ var that = this;
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ stopOnFirstInvalidFile: true
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onComplete: function(id, name, responseJSON){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){}
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'Misc data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ }
+ };
+
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ // number of files being uploaded
+ this._filesInProgress = [];
+
+ this._storedIds = [];
+
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function(){
+ return this._filesInProgress.length;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ },
+ addFiles: function(filesBlobDataOrInputs) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ index, fileOrInput;
+
+ if (filesBlobDataOrInputs) {
+ if (!window.FileList || !(filesBlobDataOrInputs instanceof FileList)) {
+ filesBlobDataOrInputs = [].concat(filesBlobDataOrInputs);
+ }
+
+ for (index = 0; index < filesBlobDataOrInputs.length; index+=1) {
+ fileOrInput = filesBlobDataOrInputs[index];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
+ this._uploadFileOrBlobDataList(verifiedFilesOrInputs);
+ }
+ },
+ addBlobs: function(blobDataOrArray) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._uploadFileOrBlobDataList(verifiedBlobDataList);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.isXhrUploadSupported(),
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result);
+ },
+ onCancel: function(id, name){
+ self._onCancel(id, name);
+ self._options.callbacks.onCancel(id, name);
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name){
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total){
+ },
+ _onComplete: function(id, name, result, xhr){
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name){
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected ||
+ (this._options.cors.expected && (qq.ie10() || !qq.ie()))
+ )
+ );
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id)) {
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ }
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(fileId) {},
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status);
+ }
+ else {
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name){},
+ _onInputChange: function(input){
+ if (qq.isXhrUploadSupported()){
+ this.addFiles(input.files);
+ } else {
+ this.addFiles(input);
+ }
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status);
+ }
+ else {
+ var errorReason = response.error ? response.error : "Upload failure reason unknown";
+ this._options.callbacks.onError(id, name, errorReason);
+ }
+ }
+ },
+ _uploadFileOrBlobDataList: function(fileOrBlobDataList){
+ var validationDescriptors, index, batchInvalid;
+
+ validationDescriptors = this._getValidationDescriptors(fileOrBlobDataList);
+ batchInvalid = this._options.callbacks.onValidateBatch(validationDescriptors) === false;
+
+ if (!batchInvalid) {
+ if (fileOrBlobDataList.length > 0) {
+ for (index = 0; index < fileOrBlobDataList.length; index++){
+ if (this._validateFileOrBlobData(fileOrBlobDataList[index])){
+ this._upload(fileOrBlobDataList[index]);
+ } else {
+ if (this._options.validation.stopOnFirstInvalidFile){
+ return;
+ }
+ }
+ }
+ }
+ else {
+ this._error('noFilesError', "");
+ }
+ }
+ },
+ _upload: function(blobOrFileContainer){
+ var id = this._handler.add(blobOrFileContainer);
+ var name = this._handler.getName(id);
+
+ if (this._options.callbacks.onSubmit(id, name) !== false){
+ this._onSubmit(id, name);
+ if (this._options.autoUpload) {
+ this._handler.upload(id);
+ }
+ else {
+ this._storeForLater(id);
+ }
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _validateFileOrBlobData: function(fileOrBlobData){
+ var validationDescriptor, name, size;
+
+ validationDescriptor = this._getValidationDescriptor(fileOrBlobData);
+ name = validationDescriptor.name;
+ size = validationDescriptor.size;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ return false;
+ }
+
+ if (qq.isFileOrInput(fileOrBlobData) && !this._isAllowedExtension(name)){
+ this._error('typeError', name);
+ return false;
+
+ }
+ else if (size === 0){
+ this._error('emptyError', name);
+ return false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._error('sizeError', name);
+ return false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._error('minSizeError', name);
+ return false;
+ }
+
+ return true;
+ },
+ _error: function(code, name){
+ var message = this._options.messages[code];
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ var extensions = this._options.validation.allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensions);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ this._options.callbacks.onError(null, name, message);
+
+ return message;
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1024;
+ i++;
+ } while (bytes > 99);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ }
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ }
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz, dirPending,
+ droppedFiles = [],
+ droppedEntriesCount = 0,
+ droppedEntriesParsedCount = 0,
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropArea: null,
+ extraDropzones: [],
+ hideDropzones: true,
+ multiple: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {},
+ error: function(code, filename) {},
+ log: function(message, level) {}
+ }
+ };
+
+ qq.extend(options, o);
+
+ function maybeUploadDroppedFiles() {
+ if (droppedEntriesCount === droppedEntriesParsedCount && !dirPending) {
+ options.callbacks.log('Grabbed ' + droppedFiles.length + " files after tree traversal.");
+ dz.dropDisabled(false);
+ options.callbacks.dropProcessing(false, droppedFiles);
+ }
+ }
+ function addDroppedFile(file) {
+ droppedFiles.push(file);
+ droppedEntriesParsedCount+=1;
+ maybeUploadDroppedFiles();
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i;
+
+ droppedEntriesCount+=1;
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ addDroppedFile(file);
+ });
+ }
+ else if (entry.isDirectory) {
+ dirPending = true;
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ droppedEntriesParsedCount+=1;
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]);
+ }
+
+ dirPending = false;
+
+ if (!entries.length) {
+ maybeUploadDroppedFiles();
+ }
+ });
+ }
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry;
+
+ options.callbacks.dropProcessing(true);
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.multiple) {
+ options.callbacks.dropProcessing(false);
+ options.callbacks.error('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ }
+ else {
+ droppedFiles = [];
+ droppedEntriesCount = 0;
+ droppedEntriesParsedCount = 0;
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ if (i === items.length-1) {
+ maybeUploadDroppedFiles();
+ }
+ }
+
+ else {
+ traverseFileTree(entry);
+ }
+ }
+ }
+ }
+ else {
+ options.callbacks.dropProcessing(false, dataTransfer.files);
+ dz.dropDisabled(false);
+ }
+ }
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer);
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ if (options.dropArea) {
+ options.extraDropzones.push(options.dropArea);
+ }
+
+ var i, dropzones = options.extraDropzones;
+
+ for (i=0; i < dropzones.length; i+=1){
+ setupDropzone(dropzones[i]);
+ }
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (options.dropArea && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ if (qq(options.dropArea).hasClass(options.classes.dropDisabled)) {
+ return;
+ }
+
+ options.dropArea.style.display = 'block';
+ for (i=0; i < dropzones.length; i+=1) {
+ dropzones[i].style.display = 'block';
+ }
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropzones && qq.FineUploader.prototype._leaving_document_out(e)) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropzones) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setup: function() {
+ setupDragDrop();
+ },
+
+ setupExtraDropzone: function(element) {
+ options.extraDropzones.push(element);
+ setupDropzone(element);
+ },
+
+ removeExtraDropzone: function(element) {
+ var i, dzs = options.extraDropzones;
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ dropDisabled: 'qq-upload-drop-area-disabled',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file"
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeExtraDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dnd, preventSelectFiles, defaultDropAreaEl;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ defaultDropAreaEl = this._find(this._options.element, 'drop');
+ }
+
+ dnd = new qq.DragAndDrop({
+ dropArea: defaultDropAreaEl,
+ extraDropzones: this._options.dragAndDrop.extraDropzones,
+ hideDropzones: this._options.dragAndDrop.hideDropzones,
+ multiple: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {
+ var input = self._button.getInput();
+
+ if (isProcessing) {
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ }
+ else {
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+ }
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ error: function(code, filename) {
+ self._error(code, filename);
+ },
+ log: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+
+ dnd.setup();
+
+ return dnd;
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type){
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name){
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ var item = this.getItemByFileId(id);
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ return false;
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id) !== false) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ }
+ }
+ else {
+ this.log("Delete request ignored for file ID " + id + ", delete feature is disabled.", "warn");
+ return false;
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ this._listElement.appendChild(item);
+
+ if (this._options.display.fileSizeOnSubmit && qq.isXhrUploadSupported()) {
+ this._displayFileSize(id);
+ }
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = target = target.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _error: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._error.apply(this, arguments);
+ this._options.showMessage(message);
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, dequeue, handlerImpl;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ dequeue = function(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.isXhrUploadSupported()) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, log);
+ }
+
+
+ return {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ return handlerImpl.upload(id);
+ }
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ handlerImpl.cancel(id);
+ dequeue(id);
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ /**
+ * Returns id of files being uploaded or
+ * waiting for their turn
+ */
+ getQueue: function(){
+ return queue;
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ queue = [];
+ handlerImpl.reset();
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var id = iframe.id;
+
+ onloadCallbacks[uuids[id]] = callback;
+
+ detachLoadEvents[id] = qq(iframe).attach('load', function() {
+ if (inputs[id]) {
+ log("Received iframe load event for CORS upload request (file id " + id + ")");
+
+ postMessageCallbackTimers[id] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for file id " + id;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(id, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = qq.parseJson(message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+
+ detachLoadEvent(id);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(id);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHTML = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHTML);
+ //plain text response may be wrapped in tag
+ if (innerHTML && innerHTML.match(/^ ');
+
+ iframe.setAttribute('id', id);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ qq.UploadHandler.prototype.reset.apply(this, arguments);
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ options.onCancel(id, this.getName(id));
+
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(id);
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ },
+ upload: function(id){
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, this.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+
+ return id;
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.isFileChunkingSupported(),
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.areCookiesEnabled(),
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+
+ options.onComplete(id, name, response, xhr);
+ delete fileState[id].xhr;
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var name = api.getName(id),
+ firstChunkIndex = 0,
+ persistedChunkInfoForResume, firstChunkDataForResume, currentChunkIndex;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part);
+ if (options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume)) !== false) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].uuid = persistedChunkInfoForResume.uuid;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+ }
+ }
+ }
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id;
+
+ if (fileOrBlobData instanceof File) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (fileOrBlobData.blob instanceof Blob) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ fileState[id].uuid = qq.getUniqueId();
+ return id;
+ },
+ getName: function(id){
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ /**
+ * Returns uploaded bytes for file identified by id
+ */
+ getLoaded: function(id){
+ return fileState[id].loaded || 0;
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry){
+ var name = this.getName(id);
+
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ },
+ cancel: function(id){
+ var xhr = fileState[id].xhr;
+
+ options.onCancel(id, this.getName(id));
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
diff --git a/ajax/libs/file-uploader/3.3.0/fineuploader.min.css b/ajax/libs/file-uploader/3.3.0/fineuploader.min.css
new file mode 100644
index 000000000..110e8fa52
--- /dev/null
+++ b/ajax/libs/file-uploader/3.3.0/fineuploader.min.css
@@ -0,0 +1 @@
+.qq-uploader{position:relative;width:100%;}.qq-upload-button{display:block;width:105px;padding:7px 0;text-align:center;background:#800;border-bottom:1px solid #DDD;color:#FFF;}.qq-upload-button-hover{background:#C00;}.qq-upload-button-focus{outline:1px dotted #000;}.qq-upload-drop-area,.qq-upload-extra-drop-area{position:absolute;top:0;left:0;width:100%;height:100%;min-height:30px;z-index:2;background:#FF9797;text-align:center;}.qq-upload-drop-area span{display:block;position:absolute;top:50%;width:100%;margin-top:-8px;font-size:16px;}.qq-upload-extra-drop-area{position:relative;margin-top:50px;font-size:16px;padding-top:30px;height:20px;min-height:40px;}.qq-upload-drop-area-active{background:#FF7171;}.qq-upload-list{margin:0;padding:0;list-style:none;}.qq-upload-list li{margin:0;padding:9px;line-height:15px;font-size:16px;background-color:#FFF0BD;}.qq-upload-file,.qq-upload-spinner,.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-failed-text,.qq-upload-finished,.qq-upload-delete{margin-right:12px;}.qq-upload-spinner{display:inline-block;background:url("loading.gif");width:15px;height:15px;vertical-align:text-bottom;}.qq-drop-processing{display:none;}.qq-drop-processing-spinner{display:inline-block;background:url("processing.gif");width:24px;height:24px;vertical-align:text-bottom;}.qq-upload-finished{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-retry,.qq-upload-delete{display:none;color:#000;}.qq-upload-cancel,.qq-upload-delete{color:#000;}.qq-upload-retryable .qq-upload-retry{display:inline;}.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-delete{font-size:12px;font-weight:normal;}.qq-upload-failed-text{display:none;font-style:italic;font-weight:bold;}.qq-upload-failed-icon{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-fail .qq-upload-failed-text{display:inline;}.qq-upload-retrying .qq-upload-failed-text{display:inline;color:#D60000;}.qq-upload-list li.qq-upload-success{background-color:#5DA30C;color:#FFF;}.qq-upload-list li.qq-upload-fail{background-color:#D60000;color:#FFF;}.qq-progress-bar{background:-moz-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,rgba(30,87,153,1)),color-stop(50%,rgba(41,137,216,1)),color-stop(51%,rgba(32,124,202,1)),color-stop(100%,rgba(125,185,232,1)));background:-webkit-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-o-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-ms-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:linear-gradient(to bottom,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);width:0;height:15px;border-radius:6px;margin-bottom:3px;display:none;}
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.3.0/fineuploader.min.js b/ajax/libs/file-uploader/3.3.0/fineuploader.min.js
new file mode 100644
index 000000000..3c9c28e73
--- /dev/null
+++ b/ajax/libs/file-uploader/3.3.0/fineuploader.min.js
@@ -0,0 +1,14 @@
+/**
+ * http://github.com/Valums-File-Uploader/file-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Original version: 1.0 © 2010 Andrew Valums ( andrew(at)valums.com )
+ * Current Maintainer (2.0+): © 2012, Ray Nicholus ( fineuploader(at)garstasio.com )
+ *
+ * Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!==null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable!==null&&variable&&typeof variable==="object"&&variable.constructor===Object};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFileOrInput=function(maybeFileOrInput){"use strict";if(qq.isBlob(maybeFileOrInput)&&window.File&&maybeFileOrInput instanceof File){return true}else if(window.HTMLInputElement){if(maybeFileOrInput instanceof HTMLInputElement){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}else if(maybeFileOrInput.tagName){if(maybeFileOrInput.tagName.toLowerCase()==="input"){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&maybeBlob instanceof Blob};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice||File.prototype.webkitSlice||File.prototype.mozSlice)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}})};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"Misc data",paramNames:{name:"qqblobname"}}};qq.extend(this._options,o,true);this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset()},addFiles:function(filesBlobDataOrInputs){var self=this,verifiedFilesOrInputs=[],index,fileOrInput;if(filesBlobDataOrInputs){if(!window.FileList||!(filesBlobDataOrInputs instanceof FileList)){filesBlobDataOrInputs=[].concat(filesBlobDataOrInputs)}for(index=0;index=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||this._options.cors.expected&&(qq.ie10()||!qq.ie()))},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)){this._deleteHandler.sendDelete(id,this.getUuid(id))}}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(fileId){},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);if(isError){this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status)}else{this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){},_onInputChange:function(input){if(qq.isXhrUploadSupported()){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0){for(index=0;indexthis._options.validation.sizeLimit){this._error("sizeError",name);return false}else if(size&&size99);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,dirPending,droppedFiles=[],droppedEntriesCount=0,droppedEntriesParsedCount=0,disposeSupport=new qq.DisposeSupport;options={dropArea:null,extraDropzones:[],hideDropzones:true,multiple:true,classes:{dropActive:null},callbacks:{dropProcessing:function(isProcessing,files){},error:function(code,filename){},log:function(message,level){}}};qq.extend(options,o);function maybeUploadDroppedFiles(){if(droppedEntriesCount===droppedEntriesParsedCount&&!dirPending){options.callbacks.log("Grabbed "+droppedFiles.length+" files after tree traversal.");dz.dropDisabled(false);options.callbacks.dropProcessing(false,droppedFiles)}}function addDroppedFile(file){droppedFiles.push(file);droppedEntriesParsedCount+=1;maybeUploadDroppedFiles()}function traverseFileTree(entry){var dirReader,i;droppedEntriesCount+=1;if(entry.isFile){entry.file(function(file){addDroppedFile(file)})}else if(entry.isDirectory){dirPending=true;dirReader=entry.createReader();dirReader.readEntries(function(entries){droppedEntriesParsedCount+=1;for(i=0;i1&&!options.multiple){options.callbacks.dropProcessing(false);options.callbacks.error("tooManyFilesError","");dz.dropDisabled(false)}else{droppedFiles=[];droppedEntriesCount=0;droppedEntriesParsedCount=0;if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",dropDisabled:"qq-upload-drop-area-disabled",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file"},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false},showMessage:function(message){setTimeout(function(){alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)}},true);qq.extend(this._options,o,true);this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop()};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeExtraDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop()},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dnd,preventSelectFiles,defaultDropAreaEl;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){defaultDropAreaEl=this._find(this._options.element,"drop")}dnd=new qq.DragAndDrop({dropArea:defaultDropAreaEl,extraDropzones:this._options.dragAndDrop.extraDropzones,hideDropzones:this._options.dragAndDrop.hideDropzones,multiple:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{dropProcessing:function(isProcessing,files){var input=self._button.getInput();if(isProcessing){qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)}else{qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles)}if(files){self.addFiles(files)}},error:function(code,filename){self._error(code,filename)},log:function(message,level){self.log(message,level)}}});dnd.setup();return dnd},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){var item=this.getItemByFileId(id);this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}return false},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)!==false){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}}}else{this.log("Delete request ignored for file ID "+id+", delete feature is disabled.","warn");return false}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}this._listElement.appendChild(item);if(this._options.display.fileSizeOnSubmit&&qq.isXhrUploadSupported()){this._displayFileSize(id)}},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=target=target.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_error:function(code,name){var message=qq.FineUploaderBasic.prototype._error.apply(this,arguments);this._options.showMessage(message)}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,dequeue,handlerImpl;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){}};qq.extend(options,o);log=options.log;dequeue=function(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){log("Cancelling "+id);options.paramsStore.remove(id);handlerImpl.cancel(id);dequeue(id)},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},getQueue:function(){return queue},reset:function(){log("Resetting upload handler");queue=[];handlerImpl.reset()},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}}};qq.UploadHandlerForm=function(o,uploadCompleteCallback,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var id=iframe.id;onloadCallbacks[uuids[id]]=callback;detachLoadEvents[id]=qq(iframe).attach("load",function(){if(inputs[id]){log("Received iframe load event for CORS upload request (file id "+id+")");postMessageCallbackTimers[id]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for file id "+id;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(id,function(message){log("Received the following window message: '"+message+"'");var response=qq.parseJson(message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];detachLoadEvent(id);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(id);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHTML=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHTML);if(innerHTML&&innerHTML.match(/^ ');iframe.setAttribute("id",id);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){return inputs[id].value.replace(/.*(\/|\\)/,"")},isValid:function(id){return inputs[id]!==undefined},reset:function(){qq.UploadHandler.prototype.reset.apply(this,arguments);inputs=[];uuids=[];detachLoadEvents={}},getUuid:function(id){return uuids[id]},cancel:function(id){options.onCancel(id,this.getName(id));delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(id);if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,this.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove();return id}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,logCallback){"use strict";var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.isFileChunkingSupported(),resumeEnabled=options.resume.enabled&&chunkFiles&&qq.areCookiesEnabled(),resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);delete fileState[id].xhr;uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;
+fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(xhr){var response;try{response=qq.parseJson(xhr.responseText)}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function handleFileChunkingUpload(id,retry){var name=api.getName(id),firstChunkIndex=0,persistedChunkInfoForResume,firstChunkDataForResume,currentChunkIndex;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part);if(options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume))!==false){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].uuid=persistedChunkInfoForResume.uuid;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex)}}}for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}}uploadNextChunk(id)}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}api={add:function(fileOrBlobData){var id;if(fileOrBlobData instanceof File){id=fileState.push({file:fileOrBlobData})-1}else if(fileOrBlobData.blob instanceof Blob){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}fileState[id].uuid=qq.getUniqueId();return id},getName:function(id){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},getLoaded:function(id){return fileState[id].loaded||0},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}},cancel:function(id){var xhr=fileState[id].xhr;options.onCancel(id,this.getName(id));if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.3.0/iframe.xss.response.js b/ajax/libs/file-uploader/3.3.0/iframe.xss.response.js
new file mode 100644
index 000000000..e11fca1e5
--- /dev/null
+++ b/ajax/libs/file-uploader/3.3.0/iframe.xss.response.js
@@ -0,0 +1,6 @@
+(function() {
+ var match = /(\{.+\}).+/.exec(document.body.innerHTML);
+ if (match) {
+ parent.postMessage(match[1], '*');
+ }
+}());
diff --git a/ajax/libs/file-uploader/3.3.0/iframe.xss.response.min.js b/ajax/libs/file-uploader/3.3.0/iframe.xss.response.min.js
new file mode 100644
index 000000000..345cf3603
--- /dev/null
+++ b/ajax/libs/file-uploader/3.3.0/iframe.xss.response.min.js
@@ -0,0 +1 @@
+!function(){var match=/(\{.+\}).+/.exec(document.body.innerHTML);if(match){parent.postMessage(match[1],"*")}}();
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.3.0/loading.gif b/ajax/libs/file-uploader/3.3.0/loading.gif
new file mode 100644
index 000000000..6fba77609
Binary files /dev/null and b/ajax/libs/file-uploader/3.3.0/loading.gif differ
diff --git a/ajax/libs/file-uploader/3.3.0/processing.gif b/ajax/libs/file-uploader/3.3.0/processing.gif
new file mode 100644
index 000000000..7c99504e1
Binary files /dev/null and b/ajax/libs/file-uploader/3.3.0/processing.gif differ
diff --git a/ajax/libs/file-uploader/3.3.1/fineuploader-jquery.js b/ajax/libs/file-uploader/3.3.1/fineuploader-jquery.js
new file mode 100644
index 000000000..87eb992e8
--- /dev/null
+++ b/ajax/libs/file-uploader/3.3.1/fineuploader-jquery.js
@@ -0,0 +1,4214 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity !== null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+ if (qq.isBlob(maybeFileOrInput) && window.File && maybeFileOrInput instanceof File) {
+ return true;
+ }
+ else if (window.HTMLInputElement) {
+ if (maybeFileOrInput instanceof HTMLInputElement) {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ else if (maybeFileOrInput.tagName) {
+ if (maybeFileOrInput.tagName.toLowerCase() === 'input') {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && maybeBlob instanceof Blob;
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice || File.prototype.webkitSlice || File.prototype.mozSlice);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(obj, callback) {
+ "use strict";
+ var key, retVal;
+ if (obj) {
+ for (key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ retVal = callback(key, obj[key]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ c;
+
+ for(var i=0;i < ca.length;i++) {
+ c = ca[i];
+ while (c.charAt(0)==' ') {
+ c = c.substring(1,c.length);
+ }
+ if (c.indexOf(nameEQ) === 0) {
+ return c.substring(nameEQ.length,c.length);
+ }
+ }
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallback, failureCallback,
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ successCallback = onSuccess;
+ failureCallback = onFailure;
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallback) {
+ successCallback(val);
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallback) {
+ failureCallback(val);
+ }
+
+ return this;
+ }
+ };
+};/*globals qq*/
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ disposeSupport = new qq.DisposeSupport(),
+ options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input) {},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+
+ qq.extend(options, o);
+
+ // make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};qq.FineUploaderBasic = function(o){
+ var that = this;
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){},
+ onPasteReceived: function(blob) {
+ return new qq.Promise().success();
+ }
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'misc_data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ }
+ };
+
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._netFilesUploadedOrQueued = 0;
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function(){
+ return this._filesInProgress.length;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netFilesUploadedOrQueued++;
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._pasteHandler.reset();
+ this._netFilesUploadedOrQueued = 0;
+ },
+ addFiles: function(filesBlobDataOrInputs) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ index, fileOrInput;
+
+ if (filesBlobDataOrInputs) {
+ if (!window.FileList || !(filesBlobDataOrInputs instanceof FileList)) {
+ filesBlobDataOrInputs = [].concat(filesBlobDataOrInputs);
+ }
+
+ for (index = 0; index < filesBlobDataOrInputs.length; index+=1) {
+ fileOrInput = filesBlobDataOrInputs[index];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
+ this._uploadFileOrBlobDataList(verifiedFilesOrInputs);
+ }
+ },
+ addBlobs: function(blobDataOrArray) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._uploadFileOrBlobDataList(verifiedBlobDataList);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getPromissoryCallbackNames: function() {
+ return ["onPasteReceived"];
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.isXhrUploadSupported(),
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result);
+ },
+ onCancel: function(id, name){
+ self._onCancel(id, name);
+ self._options.callbacks.onCancel(id, name);
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ var pasteReceivedCallback = self._options.callbacks.onPasteReceived,
+ promise = pasteReceivedCallback(blob);
+
+ if (promise.then) {
+ promise.then(function(successData) {
+ self._handlePasteSuccess(blob, successData);
+ }, function(failureData) {
+ self.log("Ignoring pasted image per paste received callback. Reason = '" + failureData + "'");
+ });
+ }
+ else {
+ self.log("Promise contract not fulfilled in pasteReceived callback handler! Ignoring pasted item.", "error");
+ }
+ }
+ }
+ });
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netFilesUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total){
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._netFilesUploadedOrQueued--;
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name){
+ this._netFilesUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected ||
+ (this._options.cors.expected && (qq.ie10() || !qq.ie()))
+ )
+ );
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id) !== false) {
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ }
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(fileId) {},
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
+ }
+ else {
+ this._netFilesUploadedOrQueued--;
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name){},
+ _onInputChange: function(input){
+ if (qq.isXhrUploadSupported()){
+ this.addFiles(input.files);
+ } else {
+ this.addFiles(input);
+ }
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netFilesUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems", "");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : "Upload failure reason unknown";
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _uploadFileOrBlobDataList: function(fileOrBlobDataList){
+ var index,
+ validationDescriptors = this._getValidationDescriptors(fileOrBlobDataList),
+ batchValid = this._isBatchValid(validationDescriptors);
+
+ if (batchValid) {
+ if (fileOrBlobDataList.length > 0) {
+ for (index = 0; index < fileOrBlobDataList.length; index++){
+ if (this._validateFileOrBlobData(fileOrBlobDataList[index])){
+ this._upload(fileOrBlobDataList[index]);
+ } else {
+ if (this._options.validation.stopOnFirstInvalidFile){
+ return;
+ }
+ }
+ }
+ }
+ else {
+ this._itemError("noFilesError", "");
+ }
+ }
+ },
+ _upload: function(blobOrFileContainer){
+ var id = this._handler.add(blobOrFileContainer);
+ var name = this._handler.getName(id);
+
+ if (this._options.callbacks.onSubmit(id, name) !== false) {
+ this._onSubmit(id, name);
+ this._options.callbacks.onSubmitted(id, name);
+
+ if (this._options.autoUpload) {
+ this._handler.upload(id);
+ }
+ else {
+ this._storeForLater(id);
+ }
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _isBatchValid: function(validationDescriptors) {
+ //first, defer the check to the callback (ask the integrator)
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netFilesUploadedOrQueued + validationDescriptors.length,
+ batchValid = this._options.callbacks.onValidateBatch(validationDescriptors) !== false;
+
+ //if the callback hasn't rejected the batch, run some internal tests on the batch next
+ if (batchValid) {
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ batchValid = true;
+ }
+ else {
+ batchValid = false;
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ }
+
+ return batchValid;
+ },
+ _validateFileOrBlobData: function(fileOrBlobData){
+ var validationDescriptor, name, size;
+
+ validationDescriptor = this._getValidationDescriptor(fileOrBlobData);
+ name = validationDescriptor.name;
+ size = validationDescriptor.size;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ return false;
+ }
+
+ if (qq.isFileOrInput(fileOrBlobData) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ return false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ return false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ return false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ return false;
+ }
+
+ return true;
+ },
+ _itemError: function(code, name) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ extensionsForMessage;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ this._options.callbacks.onError(null, name, message);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1024;
+ i++;
+ } while (bytes > 99);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz, dirPending,
+ droppedFiles = [],
+ droppedEntriesCount = 0,
+ droppedEntriesParsedCount = 0,
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropArea: null,
+ extraDropzones: [],
+ hideDropzones: true,
+ multiple: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {},
+ error: function(code, filename) {},
+ log: function(message, level) {}
+ }
+ };
+
+ qq.extend(options, o);
+
+ function maybeUploadDroppedFiles() {
+ if (droppedEntriesCount === droppedEntriesParsedCount && !dirPending) {
+ options.callbacks.log('Grabbed ' + droppedFiles.length + " files after tree traversal.");
+ dz.dropDisabled(false);
+ options.callbacks.dropProcessing(false, droppedFiles);
+ }
+ }
+ function addDroppedFile(file) {
+ droppedFiles.push(file);
+ droppedEntriesParsedCount+=1;
+ maybeUploadDroppedFiles();
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i;
+
+ droppedEntriesCount+=1;
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ addDroppedFile(file);
+ });
+ }
+ else if (entry.isDirectory) {
+ dirPending = true;
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ droppedEntriesParsedCount+=1;
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]);
+ }
+
+ dirPending = false;
+
+ if (!entries.length) {
+ maybeUploadDroppedFiles();
+ }
+ });
+ }
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry;
+
+ options.callbacks.dropProcessing(true);
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.multiple) {
+ options.callbacks.dropProcessing(false);
+ options.callbacks.error('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ }
+ else {
+ droppedFiles = [];
+ droppedEntriesCount = 0;
+ droppedEntriesParsedCount = 0;
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ if (i === items.length-1) {
+ maybeUploadDroppedFiles();
+ }
+ }
+
+ else {
+ traverseFileTree(entry);
+ }
+ }
+ }
+ }
+ else {
+ options.callbacks.dropProcessing(false, dataTransfer.files);
+ dz.dropDisabled(false);
+ }
+ }
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer);
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ if (options.dropArea) {
+ options.extraDropzones.push(options.dropArea);
+ }
+
+ var i, dropzones = options.extraDropzones;
+
+ for (i=0; i < dropzones.length; i+=1){
+ setupDropzone(dropzones[i]);
+ }
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (options.dropArea && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ if (qq(options.dropArea).hasClass(options.classes.dropDisabled)) {
+ return;
+ }
+
+ options.dropArea.style.display = 'block';
+ for (i=0; i < dropzones.length; i+=1) {
+ dropzones[i].style.display = 'block';
+ }
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropzones && qq.FineUploader.prototype._leaving_document_out(e)) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropzones) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setup: function() {
+ setupDragDrop();
+ },
+
+ setupExtraDropzone: function(element) {
+ options.extraDropzones.push(element);
+ setupDropzone(element);
+ },
+
+ removeExtraDropzone: function(element) {
+ var i, dzs = options.extraDropzones;
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ dropDisabled: 'qq-upload-drop-area-disabled',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file"
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeExtraDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dnd, preventSelectFiles, defaultDropAreaEl;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ defaultDropAreaEl = this._find(this._options.element, 'drop');
+ }
+
+ dnd = new qq.DragAndDrop({
+ dropArea: defaultDropAreaEl,
+ extraDropzones: this._options.dragAndDrop.extraDropzones,
+ hideDropzones: this._options.dragAndDrop.hideDropzones,
+ multiple: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {
+ var input = self._button.getInput();
+
+ if (isProcessing) {
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ }
+ else {
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+ }
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ error: function(code, filename) {
+ self._itemError(code, filename);
+ },
+ log: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+
+ dnd.setup();
+
+ return dnd;
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type){
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name){
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id) !== false) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ }
+ }
+ else {
+ this.log("Delete request ignored for file ID " + id + ", delete feature is disabled.", "warn");
+ return false;
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ this._listElement.appendChild(item);
+
+ if (this._options.display.fileSizeOnSubmit && qq.isXhrUploadSupported()) {
+ this._displayFileSize(id);
+ }
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = target = target.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, dequeue, handlerImpl;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ dequeue = function(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.isXhrUploadSupported()) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, log);
+ }
+
+
+ return {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ return handlerImpl.upload(id);
+ }
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ handlerImpl.cancel(id);
+ dequeue(id);
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ /**
+ * Returns id of files being uploaded or
+ * waiting for their turn
+ */
+ getQueue: function(){
+ return queue;
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ queue = [];
+ handlerImpl.reset();
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var id = iframe.id;
+
+ onloadCallbacks[uuids[id]] = callback;
+
+ detachLoadEvents[id] = qq(iframe).attach('load', function() {
+ if (inputs[id]) {
+ log("Received iframe load event for CORS upload request (file id " + id + ")");
+
+ postMessageCallbackTimers[id] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for file id " + id;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(id, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = qq.parseJson(message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+
+ detachLoadEvent(id);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(id);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHTML = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHTML);
+ //plain text response may be wrapped in tag
+ if (innerHTML && innerHTML.match(/^ ');
+
+ iframe.setAttribute('id', id);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ options.onCancel(id, this.getName(id));
+
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(id);
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ },
+ upload: function(id){
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, this.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+
+ return id;
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.isFileChunkingSupported(),
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.areCookiesEnabled(),
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+
+ options.onComplete(id, name, response, xhr);
+ delete fileState[id].xhr;
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var name = api.getName(id),
+ firstChunkIndex = 0,
+ persistedChunkInfoForResume, firstChunkDataForResume, currentChunkIndex;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part);
+ if (options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume)) !== false) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].uuid = persistedChunkInfoForResume.uuid;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+ }
+ }
+ }
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id;
+
+ if (fileOrBlobData instanceof File) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (fileOrBlobData.blob instanceof Blob) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ fileState[id].uuid = qq.getUniqueId();
+ return id;
+ },
+ getName: function(id){
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ /**
+ * Returns uploaded bytes for file identified by id
+ */
+ getLoaded: function(id){
+ return fileState[id].loaded || 0;
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry){
+ var name = this.getName(id);
+
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ },
+ cancel: function(id){
+ var xhr = fileState[id].xhr;
+
+ options.onCancel(id, this.getName(id));
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var uploader, $el, init, dataStore, pluginOption, pluginOptions, addCallbacks, transformVariables, isValidCommand,
+ delegateCommand;
+
+ pluginOptions = ['uploaderType'];
+
+ init = function (options) {
+ if (options) {
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+
+ if (pluginOption('uploaderType') === 'basic') {
+ uploader(new qq.FineUploaderBasic(xformedOpts));
+ }
+ else {
+ uploader(new qq.FineUploader(xformedOpts));
+ }
+ }
+
+ return $el;
+ };
+
+ dataStore = function(key, val) {
+ var data = $el.data('fineuploader');
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data('fineuploader', data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ //the underlying Fine Uploader instance is stored in jQuery's data stored, associated with the element
+ // tied to this instance of the plug-in
+ uploader = function(instanceToStore) {
+ return dataStore('uploader', instanceToStore);
+ };
+
+ pluginOption = function(option, optionVal) {
+ return dataStore(option, optionVal);
+ };
+
+ //implement all callbacks defined in Fine Uploader as functions that trigger appropriately names events and
+ // return the result of executing the bound handler back to Fine Uploader
+ addCallbacks = function(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {},
+ uploaderInst = new qq.FineUploaderBasic();
+
+ $.each(uploaderInst._options.callbacks, function(prop, func) {
+ var name, $callbackEl;
+
+ name = /^on(\w+)/.exec(prop)[1];
+ name = name.substring(0, 1).toLowerCase() + name.substring(1);
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var origFunc = func,
+ args = Array.prototype.slice.call(arguments),
+ jqueryHandlerResult = $callbackEl.triggerHandler(name, args);
+
+ if (jqueryHandlerResult === undefined &&
+ $.inArray(prop, uploaderInst.getPromissoryCallbackNames()) >= 0) {
+ return origFunc();
+ }
+
+ return jqueryHandlerResult;
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ transformVariables = function(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ if (source.uploaderType !== 'basic') {
+ xformed = { element : $el[0] };
+ }
+ else {
+ xformed = {};
+ }
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if ($.inArray(prop, pluginOptions) >= 0) {
+ pluginOption(prop, val);
+ }
+ else if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ isValidCommand = function(command) {
+ return $.type(command) === "string" &&
+ !command.match(/^_/) && //enforce private methods convention
+ uploader()[command] !== undefined;
+ };
+
+ //assuming we have already verified that this is a valid command, call the associated function in the underlying
+ // Fine Uploader instance (passing along the arguments from the caller) and return the result of the call back to the caller
+ delegateCommand = function(command) {
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
+
+ transformVariables(origArgs, xformedArgs);
+
+ return uploader()[command].apply(uploader(), xformedArgs);
+ };
+
+ $.fn.fineUploader = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (uploader() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error('Method ' + optionsOrCommand + ' does not exist on jQuery.fineUploader');
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
diff --git a/ajax/libs/file-uploader/3.3.1/fineuploader-jquery.min.js b/ajax/libs/file-uploader/3.3.1/fineuploader-jquery.min.js
new file mode 100644
index 000000000..039415384
--- /dev/null
+++ b/ajax/libs/file-uploader/3.3.1/fineuploader-jquery.min.js
@@ -0,0 +1,13 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!==null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable!==null&&variable&&typeof variable==="object"&&variable.constructor===Object};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isString=function(maybeString){"use strict";return Object.prototype.toString.call(maybeString)==="[object String]"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFileOrInput=function(maybeFileOrInput){"use strict";if(qq.isBlob(maybeFileOrInput)&&window.File&&maybeFileOrInput instanceof File){return true}else if(window.HTMLInputElement){if(maybeFileOrInput instanceof HTMLInputElement){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}else if(maybeFileOrInput.tagName){if(maybeFileOrInput.tagName.toLowerCase()==="input"){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&maybeBlob instanceof Blob};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice||File.prototype.webkitSlice||File.prototype.mozSlice)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}})};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"misc_data",paramNames:{name:"qqblobname"}},paste:{targetElement:null,defaultName:"pasted_image"}};qq.extend(this._options,o,true);this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._netFilesUploadedOrQueued=0;this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}if(this._options.paste.targetElement){this._pasteHandler=this._createPasteHandler()}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._netFilesUploadedOrQueued++;this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset();this._pasteHandler.reset();this._netFilesUploadedOrQueued=0},addFiles:function(filesBlobDataOrInputs){var self=this,verifiedFilesOrInputs=[],index,fileOrInput;if(filesBlobDataOrInputs){if(!window.FileList||!(filesBlobDataOrInputs instanceof FileList)){filesBlobDataOrInputs=[].concat(filesBlobDataOrInputs)}for(index=0;index=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||this._options.cors.expected&&(qq.ie10()||!qq.ie()))},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)!==false){this._deleteHandler.sendDelete(id,this.getUuid(id))}}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(fileId){},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);if(isError){this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status,xhr)}else{this._netFilesUploadedOrQueued--;this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){},_onInputChange:function(input){if(qq.isXhrUploadSupported()){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0&&this._netFilesUploadedOrQueued+1>itemLimit){this._itemError("retryFailTooManyItems","");return false}this.log("Retrying upload for '"+fileName+"' (id: "+id+")...");this._filesInProgress.push(id);return true}else{this.log("'"+id+"' is not a valid file ID","error");return false}},_maybeParseAndSendUploadError:function(id,name,response,xhr){if(!response.success){if(xhr&&xhr.status!==200&&!response.error){this._options.callbacks.onError(id,name,"XHR returned response code "+xhr.status,xhr)}else{var errorReason=response.error?response.error:"Upload failure reason unknown";this._options.callbacks.onError(id,name,errorReason,xhr)}}},_uploadFileOrBlobDataList:function(fileOrBlobDataList){var index,validationDescriptors=this._getValidationDescriptors(fileOrBlobDataList),batchValid=this._isBatchValid(validationDescriptors);if(batchValid){if(fileOrBlobDataList.length>0){for(index=0;indexthis._options.validation.sizeLimit){this._itemError("sizeError",name);return false}else if(size&&size99);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,dirPending,droppedFiles=[],droppedEntriesCount=0,droppedEntriesParsedCount=0,disposeSupport=new qq.DisposeSupport;options={dropArea:null,extraDropzones:[],hideDropzones:true,multiple:true,classes:{dropActive:null},callbacks:{dropProcessing:function(isProcessing,files){},error:function(code,filename){},log:function(message,level){}}};
+qq.extend(options,o);function maybeUploadDroppedFiles(){if(droppedEntriesCount===droppedEntriesParsedCount&&!dirPending){options.callbacks.log("Grabbed "+droppedFiles.length+" files after tree traversal.");dz.dropDisabled(false);options.callbacks.dropProcessing(false,droppedFiles)}}function addDroppedFile(file){droppedFiles.push(file);droppedEntriesParsedCount+=1;maybeUploadDroppedFiles()}function traverseFileTree(entry){var dirReader,i;droppedEntriesCount+=1;if(entry.isFile){entry.file(function(file){addDroppedFile(file)})}else if(entry.isDirectory){dirPending=true;dirReader=entry.createReader();dirReader.readEntries(function(entries){droppedEntriesParsedCount+=1;for(i=0;i1&&!options.multiple){options.callbacks.dropProcessing(false);options.callbacks.error("tooManyFilesError","");dz.dropDisabled(false)}else{droppedFiles=[];droppedEntriesCount=0;droppedEntriesParsedCount=0;if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",dropDisabled:"qq-upload-drop-area-disabled",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file"},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false},paste:{promptForName:false,namePromptMessage:"Please name this image"},showMessage:function(message){setTimeout(function(){window.alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=window.confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)},showPrompt:function(message,defaultValue){var promise=new qq.Promise,retVal=window.prompt(message,defaultValue);if(retVal!=null&&qq.trimStr(retVal).length>0){promise.success(retVal)}else{promise.failure("Undefined or invalid user-supplied value.")}return promise}},true);qq.extend(this._options,o,true);this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop();if(this._options.paste.targetElement&&this._options.paste.promptForName){this._setupPastePrompt()}};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeExtraDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop()},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dnd,preventSelectFiles,defaultDropAreaEl;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){defaultDropAreaEl=this._find(this._options.element,"drop")}dnd=new qq.DragAndDrop({dropArea:defaultDropAreaEl,extraDropzones:this._options.dragAndDrop.extraDropzones,hideDropzones:this._options.dragAndDrop.hideDropzones,multiple:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{dropProcessing:function(isProcessing,files){var input=self._button.getInput();if(isProcessing){qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)}else{qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles)}if(files){self.addFiles(files)}},error:function(code,filename){self._itemError(code,filename)},log:function(message,level){self.log(message,level)}}});dnd.setup();return dnd},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){var item=this.getItemByFileId(id);if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}else{qq(item).addClass(this._classes.retryable);return false}},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)!==false){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}}}else{this.log("Delete request ignored for file ID "+id+", delete feature is disabled.","warn");return false}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}this._listElement.appendChild(item);if(this._options.display.fileSizeOnSubmit&&qq.isXhrUploadSupported()){this._displayFileSize(id)}},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=target=target.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_itemError:function(code,name){var message=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(message)},_batchError:function(message){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments);this._options.showMessage(message)},_setupPastePrompt:function(){var self=this;this._options.callbacks.onPasteReceived=function(){var message=self._options.paste.namePromptMessage,defaultVal=self._options.paste.defaultName;return self._options.showPrompt(message,defaultVal)}}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,dequeue,handlerImpl;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){}};qq.extend(options,o);log=options.log;dequeue=function(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){log("Cancelling "+id);options.paramsStore.remove(id);handlerImpl.cancel(id);dequeue(id)},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},getQueue:function(){return queue},reset:function(){log("Resetting upload handler");queue=[];handlerImpl.reset()},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}}};qq.UploadHandlerForm=function(o,uploadCompleteCallback,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var id=iframe.id;onloadCallbacks[uuids[id]]=callback;detachLoadEvents[id]=qq(iframe).attach("load",function(){if(inputs[id]){log("Received iframe load event for CORS upload request (file id "+id+")");postMessageCallbackTimers[id]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for file id "+id;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(id,function(message){log("Received the following window message: '"+message+"'");var response=qq.parseJson(message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];detachLoadEvent(id);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(id);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHTML=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHTML);if(innerHTML&&innerHTML.match(/^ ');iframe.setAttribute("id",id);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){if(api.isValid(id)){return inputs[id].value.replace(/.*(\/|\\)/,"")}else{log(id+" is not a valid item ID.","error")}},isValid:function(id){return inputs[id]!==undefined},reset:function(){inputs=[];uuids=[];detachLoadEvents={}},getUuid:function(id){return uuids[id]},cancel:function(id){options.onCancel(id,this.getName(id));delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(id);if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,this.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove();return id}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,logCallback){"use strict";
+var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.isFileChunkingSupported(),resumeEnabled=options.resume.enabled&&chunkFiles&&qq.areCookiesEnabled(),resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);delete fileState[id].xhr;uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(xhr){var response;try{response=qq.parseJson(xhr.responseText)}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function handleFileChunkingUpload(id,retry){var name=api.getName(id),firstChunkIndex=0,persistedChunkInfoForResume,firstChunkDataForResume,currentChunkIndex;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part);if(options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume))!==false){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].uuid=persistedChunkInfoForResume.uuid;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex)}}}for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}}uploadNextChunk(id)}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}api={add:function(fileOrBlobData){var id;if(fileOrBlobData instanceof File){id=fileState.push({file:fileOrBlobData})-1}else if(fileOrBlobData.blob instanceof Blob){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}fileState[id].uuid=qq.getUniqueId();return id},getName:function(id){if(api.isValid(id)){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}}else{log(id+" is not a valid item ID.","error")}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},getLoaded:function(id){return fileState[id].loaded||0},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}},cancel:function(id){var xhr=fileState[id].xhr;options.onCancel(id,this.getName(id));if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};!function($){"use strict";var uploader,$el,init,dataStore,pluginOption,pluginOptions,addCallbacks,transformVariables,isValidCommand,delegateCommand;pluginOptions=["uploaderType"];init=function(options){if(options){var xformedOpts=transformVariables(options);addCallbacks(xformedOpts);if(pluginOption("uploaderType")==="basic"){uploader(new qq.FineUploaderBasic(xformedOpts))}else{uploader(new qq.FineUploader(xformedOpts))}}return $el};dataStore=function(key,val){var data=$el.data("fineuploader");if(val){if(data===undefined){data={}}data[key]=val;$el.data("fineuploader",data)}else{if(data===undefined){return null}return data[key]}};uploader=function(instanceToStore){return dataStore("uploader",instanceToStore)};pluginOption=function(option,optionVal){return dataStore(option,optionVal)};addCallbacks=function(transformedOpts){var callbacks=transformedOpts.callbacks={},uploaderInst=new qq.FineUploaderBasic;$.each(uploaderInst._options.callbacks,function(prop,func){var name,$callbackEl;name=/^on(\w+)/.exec(prop)[1];name=name.substring(0,1).toLowerCase()+name.substring(1);$callbackEl=$el;callbacks[prop]=function(){var origFunc=func,args=Array.prototype.slice.call(arguments),jqueryHandlerResult=$callbackEl.triggerHandler(name,args);if(jqueryHandlerResult===undefined&&$.inArray(prop,uploaderInst.getPromissoryCallbackNames())>=0){return origFunc()}return jqueryHandlerResult}})};transformVariables=function(source,dest){var xformed,arrayVals;if(dest===undefined){if(source.uploaderType!=="basic"){xformed={element:$el[0]}}else{xformed={}}}else{xformed=dest}$.each(source,function(prop,val){if($.inArray(prop,pluginOptions)>=0){pluginOption(prop,val)}else if(val instanceof $){xformed[prop]=val[0]}else if($.isPlainObject(val)){xformed[prop]={};transformVariables(val,xformed[prop])}else if($.isArray(val)){arrayVals=[];$.each(val,function(idx,arrayVal){if(arrayVal instanceof $){$.merge(arrayVals,arrayVal)}else{arrayVals.push(arrayVal)}});xformed[prop]=arrayVals}else{xformed[prop]=val}});if(dest===undefined){return xformed}};isValidCommand=function(command){return $.type(command)==="string"&&!command.match(/^_/)&&uploader()[command]!==undefined};delegateCommand=function(command){var xformedArgs=[],origArgs=Array.prototype.slice.call(arguments,1);transformVariables(origArgs,xformedArgs);return uploader()[command].apply(uploader(),xformedArgs)};$.fn.fineUploader=function(optionsOrCommand){var self=this,selfArgs=arguments,retVals=[];this.each(function(index,el){$el=$(el);if(uploader()&&isValidCommand(optionsOrCommand)){retVals.push(delegateCommand.apply(self,selfArgs));if(self.length===1){return false}}else if(typeof optionsOrCommand==="object"||!optionsOrCommand){init.apply(self,selfArgs)}else{$.error("Method "+optionsOrCommand+" does not exist on jQuery.fineUploader")}});if(retVals.length===1){return retVals[0]}else if(retVals.length>1){return retVals}return this}}(jQuery);
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.3.1/fineuploader.css b/ajax/libs/file-uploader/3.3.1/fineuploader.css
new file mode 100644
index 000000000..11a436a06
--- /dev/null
+++ b/ajax/libs/file-uploader/3.3.1/fineuploader.css
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2013, Widen Enterprises info@fineuploader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+.qq-uploader {
+ position: relative;
+ width: 100%;
+}
+.qq-upload-button {
+ display: block;
+ width: 105px;
+ padding: 7px 0;
+ text-align: center;
+ background: #880000;
+ border-bottom: 1px solid #DDD;
+ color: #FFF;
+}
+.qq-upload-button-hover {
+ background: #CC0000;
+}
+.qq-upload-button-focus {
+ outline: 1px dotted #000000;
+}
+.qq-upload-drop-area, .qq-upload-extra-drop-area {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ min-height: 30px;
+ z-index: 2;
+ background: #FF9797;
+ text-align: center;
+}
+.qq-upload-drop-area span {
+ display: block;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ margin-top: -8px;
+ font-size: 16px;
+}
+.qq-upload-extra-drop-area {
+ position: relative;
+ margin-top: 50px;
+ font-size: 16px;
+ padding-top: 30px;
+ height: 20px;
+ min-height: 40px;
+}
+.qq-upload-drop-area-active {
+ background: #FF7171;
+}
+.qq-upload-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+.qq-upload-list li {
+ margin: 0;
+ padding: 9px;
+ line-height: 15px;
+ font-size: 16px;
+ background-color: #FFF0BD;
+}
+.qq-upload-file, .qq-upload-spinner, .qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text, .qq-upload-finished, .qq-upload-delete {
+ margin-right: 12px;
+}
+.qq-upload-file {
+}
+.qq-upload-spinner {
+ display: inline-block;
+ background: url("loading.gif");
+ width: 15px;
+ height: 15px;
+ vertical-align: text-bottom;
+}
+.qq-drop-processing {
+ display: none;
+}
+.qq-drop-processing-spinner {
+ display: inline-block;
+ background: url("processing.gif");
+ width: 24px;
+ height: 24px;
+ vertical-align: text-bottom;
+}
+.qq-upload-finished {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-retry, .qq-upload-delete {
+ display: none;
+ color: #000000;
+}
+.qq-upload-cancel, .qq-upload-delete {
+ color: #000000;
+}
+.qq-upload-retryable .qq-upload-retry {
+ display: inline;
+}
+.qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-delete {
+ font-size: 12px;
+ font-weight: normal;
+}
+.qq-upload-failed-text {
+ display: none;
+ font-style: italic;
+ font-weight: bold;
+}
+.qq-upload-failed-icon {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-fail .qq-upload-failed-text {
+ display: inline;
+}
+.qq-upload-retrying .qq-upload-failed-text {
+ display: inline;
+ color: #D60000;
+}
+.qq-upload-list li.qq-upload-success {
+ background-color: #5DA30C;
+ color: #FFFFFF;
+}
+.qq-upload-list li.qq-upload-fail {
+ background-color: #D60000;
+ color: #FFFFFF;
+}
+.qq-progress-bar {
+ background: -moz-linear-gradient(top, rgba(30,87,153,1) 0%, rgba(41,137,216,1) 50%, rgba(32,124,202,1) 51%, rgba(125,185,232,1) 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(30,87,153,1)), color-stop(50%,rgba(41,137,216,1)), color-stop(51%,rgba(32,124,202,1)), color-stop(100%,rgba(125,185,232,1))); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* IE10+ */
+ background: linear-gradient(to bottom, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* W3C */
+ width: 0%;
+ height: 15px;
+ border-radius: 6px;
+ margin-bottom: 3px;
+ display: none;
+}
diff --git a/ajax/libs/file-uploader/3.3.1/fineuploader.js b/ajax/libs/file-uploader/3.3.1/fineuploader.js
new file mode 100644
index 000000000..d9dc9c79c
--- /dev/null
+++ b/ajax/libs/file-uploader/3.3.1/fineuploader.js
@@ -0,0 +1,4037 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity !== null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+ if (qq.isBlob(maybeFileOrInput) && window.File && maybeFileOrInput instanceof File) {
+ return true;
+ }
+ else if (window.HTMLInputElement) {
+ if (maybeFileOrInput instanceof HTMLInputElement) {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ else if (maybeFileOrInput.tagName) {
+ if (maybeFileOrInput.tagName.toLowerCase() === 'input') {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && maybeBlob instanceof Blob;
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice || File.prototype.webkitSlice || File.prototype.mozSlice);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(obj, callback) {
+ "use strict";
+ var key, retVal;
+ if (obj) {
+ for (key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ retVal = callback(key, obj[key]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ c;
+
+ for(var i=0;i < ca.length;i++) {
+ c = ca[i];
+ while (c.charAt(0)==' ') {
+ c = c.substring(1,c.length);
+ }
+ if (c.indexOf(nameEQ) === 0) {
+ return c.substring(nameEQ.length,c.length);
+ }
+ }
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallback, failureCallback,
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ successCallback = onSuccess;
+ failureCallback = onFailure;
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallback) {
+ successCallback(val);
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallback) {
+ failureCallback(val);
+ }
+
+ return this;
+ }
+ };
+};/*globals qq*/
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ disposeSupport = new qq.DisposeSupport(),
+ options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input) {},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+
+ qq.extend(options, o);
+
+ // make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};qq.FineUploaderBasic = function(o){
+ var that = this;
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){},
+ onPasteReceived: function(blob) {
+ return new qq.Promise().success();
+ }
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'misc_data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ }
+ };
+
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._netFilesUploadedOrQueued = 0;
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function(){
+ return this._filesInProgress.length;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netFilesUploadedOrQueued++;
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._pasteHandler.reset();
+ this._netFilesUploadedOrQueued = 0;
+ },
+ addFiles: function(filesBlobDataOrInputs) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ index, fileOrInput;
+
+ if (filesBlobDataOrInputs) {
+ if (!window.FileList || !(filesBlobDataOrInputs instanceof FileList)) {
+ filesBlobDataOrInputs = [].concat(filesBlobDataOrInputs);
+ }
+
+ for (index = 0; index < filesBlobDataOrInputs.length; index+=1) {
+ fileOrInput = filesBlobDataOrInputs[index];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
+ this._uploadFileOrBlobDataList(verifiedFilesOrInputs);
+ }
+ },
+ addBlobs: function(blobDataOrArray) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._uploadFileOrBlobDataList(verifiedBlobDataList);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getPromissoryCallbackNames: function() {
+ return ["onPasteReceived"];
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.isXhrUploadSupported(),
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result);
+ },
+ onCancel: function(id, name){
+ self._onCancel(id, name);
+ self._options.callbacks.onCancel(id, name);
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ var pasteReceivedCallback = self._options.callbacks.onPasteReceived,
+ promise = pasteReceivedCallback(blob);
+
+ if (promise.then) {
+ promise.then(function(successData) {
+ self._handlePasteSuccess(blob, successData);
+ }, function(failureData) {
+ self.log("Ignoring pasted image per paste received callback. Reason = '" + failureData + "'");
+ });
+ }
+ else {
+ self.log("Promise contract not fulfilled in pasteReceived callback handler! Ignoring pasted item.", "error");
+ }
+ }
+ }
+ });
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netFilesUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total){
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._netFilesUploadedOrQueued--;
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name){
+ this._netFilesUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected ||
+ (this._options.cors.expected && (qq.ie10() || !qq.ie()))
+ )
+ );
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id) !== false) {
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ }
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(fileId) {},
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
+ }
+ else {
+ this._netFilesUploadedOrQueued--;
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name){},
+ _onInputChange: function(input){
+ if (qq.isXhrUploadSupported()){
+ this.addFiles(input.files);
+ } else {
+ this.addFiles(input);
+ }
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netFilesUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems", "");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : "Upload failure reason unknown";
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _uploadFileOrBlobDataList: function(fileOrBlobDataList){
+ var index,
+ validationDescriptors = this._getValidationDescriptors(fileOrBlobDataList),
+ batchValid = this._isBatchValid(validationDescriptors);
+
+ if (batchValid) {
+ if (fileOrBlobDataList.length > 0) {
+ for (index = 0; index < fileOrBlobDataList.length; index++){
+ if (this._validateFileOrBlobData(fileOrBlobDataList[index])){
+ this._upload(fileOrBlobDataList[index]);
+ } else {
+ if (this._options.validation.stopOnFirstInvalidFile){
+ return;
+ }
+ }
+ }
+ }
+ else {
+ this._itemError("noFilesError", "");
+ }
+ }
+ },
+ _upload: function(blobOrFileContainer){
+ var id = this._handler.add(blobOrFileContainer);
+ var name = this._handler.getName(id);
+
+ if (this._options.callbacks.onSubmit(id, name) !== false) {
+ this._onSubmit(id, name);
+ this._options.callbacks.onSubmitted(id, name);
+
+ if (this._options.autoUpload) {
+ this._handler.upload(id);
+ }
+ else {
+ this._storeForLater(id);
+ }
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _isBatchValid: function(validationDescriptors) {
+ //first, defer the check to the callback (ask the integrator)
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netFilesUploadedOrQueued + validationDescriptors.length,
+ batchValid = this._options.callbacks.onValidateBatch(validationDescriptors) !== false;
+
+ //if the callback hasn't rejected the batch, run some internal tests on the batch next
+ if (batchValid) {
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ batchValid = true;
+ }
+ else {
+ batchValid = false;
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ }
+
+ return batchValid;
+ },
+ _validateFileOrBlobData: function(fileOrBlobData){
+ var validationDescriptor, name, size;
+
+ validationDescriptor = this._getValidationDescriptor(fileOrBlobData);
+ name = validationDescriptor.name;
+ size = validationDescriptor.size;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ return false;
+ }
+
+ if (qq.isFileOrInput(fileOrBlobData) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ return false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ return false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ return false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ return false;
+ }
+
+ return true;
+ },
+ _itemError: function(code, name) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ extensionsForMessage;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ this._options.callbacks.onError(null, name, message);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1024;
+ i++;
+ } while (bytes > 99);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz, dirPending,
+ droppedFiles = [],
+ droppedEntriesCount = 0,
+ droppedEntriesParsedCount = 0,
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropArea: null,
+ extraDropzones: [],
+ hideDropzones: true,
+ multiple: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {},
+ error: function(code, filename) {},
+ log: function(message, level) {}
+ }
+ };
+
+ qq.extend(options, o);
+
+ function maybeUploadDroppedFiles() {
+ if (droppedEntriesCount === droppedEntriesParsedCount && !dirPending) {
+ options.callbacks.log('Grabbed ' + droppedFiles.length + " files after tree traversal.");
+ dz.dropDisabled(false);
+ options.callbacks.dropProcessing(false, droppedFiles);
+ }
+ }
+ function addDroppedFile(file) {
+ droppedFiles.push(file);
+ droppedEntriesParsedCount+=1;
+ maybeUploadDroppedFiles();
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i;
+
+ droppedEntriesCount+=1;
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ addDroppedFile(file);
+ });
+ }
+ else if (entry.isDirectory) {
+ dirPending = true;
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ droppedEntriesParsedCount+=1;
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]);
+ }
+
+ dirPending = false;
+
+ if (!entries.length) {
+ maybeUploadDroppedFiles();
+ }
+ });
+ }
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry;
+
+ options.callbacks.dropProcessing(true);
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.multiple) {
+ options.callbacks.dropProcessing(false);
+ options.callbacks.error('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ }
+ else {
+ droppedFiles = [];
+ droppedEntriesCount = 0;
+ droppedEntriesParsedCount = 0;
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ if (i === items.length-1) {
+ maybeUploadDroppedFiles();
+ }
+ }
+
+ else {
+ traverseFileTree(entry);
+ }
+ }
+ }
+ }
+ else {
+ options.callbacks.dropProcessing(false, dataTransfer.files);
+ dz.dropDisabled(false);
+ }
+ }
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer);
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ if (options.dropArea) {
+ options.extraDropzones.push(options.dropArea);
+ }
+
+ var i, dropzones = options.extraDropzones;
+
+ for (i=0; i < dropzones.length; i+=1){
+ setupDropzone(dropzones[i]);
+ }
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (options.dropArea && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ if (qq(options.dropArea).hasClass(options.classes.dropDisabled)) {
+ return;
+ }
+
+ options.dropArea.style.display = 'block';
+ for (i=0; i < dropzones.length; i+=1) {
+ dropzones[i].style.display = 'block';
+ }
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropzones && qq.FineUploader.prototype._leaving_document_out(e)) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropzones) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setup: function() {
+ setupDragDrop();
+ },
+
+ setupExtraDropzone: function(element) {
+ options.extraDropzones.push(element);
+ setupDropzone(element);
+ },
+
+ removeExtraDropzone: function(element) {
+ var i, dzs = options.extraDropzones;
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ dropDisabled: 'qq-upload-drop-area-disabled',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file"
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeExtraDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dnd, preventSelectFiles, defaultDropAreaEl;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ defaultDropAreaEl = this._find(this._options.element, 'drop');
+ }
+
+ dnd = new qq.DragAndDrop({
+ dropArea: defaultDropAreaEl,
+ extraDropzones: this._options.dragAndDrop.extraDropzones,
+ hideDropzones: this._options.dragAndDrop.hideDropzones,
+ multiple: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {
+ var input = self._button.getInput();
+
+ if (isProcessing) {
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ }
+ else {
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+ }
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ error: function(code, filename) {
+ self._itemError(code, filename);
+ },
+ log: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+
+ dnd.setup();
+
+ return dnd;
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type){
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name){
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id) !== false) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ }
+ }
+ else {
+ this.log("Delete request ignored for file ID " + id + ", delete feature is disabled.", "warn");
+ return false;
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ this._listElement.appendChild(item);
+
+ if (this._options.display.fileSizeOnSubmit && qq.isXhrUploadSupported()) {
+ this._displayFileSize(id);
+ }
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = target = target.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, dequeue, handlerImpl;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ dequeue = function(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.isXhrUploadSupported()) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, log);
+ }
+
+
+ return {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ return handlerImpl.upload(id);
+ }
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ handlerImpl.cancel(id);
+ dequeue(id);
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ /**
+ * Returns id of files being uploaded or
+ * waiting for their turn
+ */
+ getQueue: function(){
+ return queue;
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ queue = [];
+ handlerImpl.reset();
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var id = iframe.id;
+
+ onloadCallbacks[uuids[id]] = callback;
+
+ detachLoadEvents[id] = qq(iframe).attach('load', function() {
+ if (inputs[id]) {
+ log("Received iframe load event for CORS upload request (file id " + id + ")");
+
+ postMessageCallbackTimers[id] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for file id " + id;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(id, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = qq.parseJson(message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+
+ detachLoadEvent(id);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(id);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHTML = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHTML);
+ //plain text response may be wrapped in tag
+ if (innerHTML && innerHTML.match(/^ ');
+
+ iframe.setAttribute('id', id);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ options.onCancel(id, this.getName(id));
+
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(id);
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ },
+ upload: function(id){
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, this.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+
+ return id;
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.isFileChunkingSupported(),
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.areCookiesEnabled(),
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+
+ options.onComplete(id, name, response, xhr);
+ delete fileState[id].xhr;
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var name = api.getName(id),
+ firstChunkIndex = 0,
+ persistedChunkInfoForResume, firstChunkDataForResume, currentChunkIndex;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part);
+ if (options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume)) !== false) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].uuid = persistedChunkInfoForResume.uuid;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+ }
+ }
+ }
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id;
+
+ if (fileOrBlobData instanceof File) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (fileOrBlobData.blob instanceof Blob) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ fileState[id].uuid = qq.getUniqueId();
+ return id;
+ },
+ getName: function(id){
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ /**
+ * Returns uploaded bytes for file identified by id
+ */
+ getLoaded: function(id){
+ return fileState[id].loaded || 0;
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry){
+ var name = this.getName(id);
+
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ },
+ cancel: function(id){
+ var xhr = fileState[id].xhr;
+
+ options.onCancel(id, this.getName(id));
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
diff --git a/ajax/libs/file-uploader/3.3.1/fineuploader.min.css b/ajax/libs/file-uploader/3.3.1/fineuploader.min.css
new file mode 100644
index 000000000..110e8fa52
--- /dev/null
+++ b/ajax/libs/file-uploader/3.3.1/fineuploader.min.css
@@ -0,0 +1 @@
+.qq-uploader{position:relative;width:100%;}.qq-upload-button{display:block;width:105px;padding:7px 0;text-align:center;background:#800;border-bottom:1px solid #DDD;color:#FFF;}.qq-upload-button-hover{background:#C00;}.qq-upload-button-focus{outline:1px dotted #000;}.qq-upload-drop-area,.qq-upload-extra-drop-area{position:absolute;top:0;left:0;width:100%;height:100%;min-height:30px;z-index:2;background:#FF9797;text-align:center;}.qq-upload-drop-area span{display:block;position:absolute;top:50%;width:100%;margin-top:-8px;font-size:16px;}.qq-upload-extra-drop-area{position:relative;margin-top:50px;font-size:16px;padding-top:30px;height:20px;min-height:40px;}.qq-upload-drop-area-active{background:#FF7171;}.qq-upload-list{margin:0;padding:0;list-style:none;}.qq-upload-list li{margin:0;padding:9px;line-height:15px;font-size:16px;background-color:#FFF0BD;}.qq-upload-file,.qq-upload-spinner,.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-failed-text,.qq-upload-finished,.qq-upload-delete{margin-right:12px;}.qq-upload-spinner{display:inline-block;background:url("loading.gif");width:15px;height:15px;vertical-align:text-bottom;}.qq-drop-processing{display:none;}.qq-drop-processing-spinner{display:inline-block;background:url("processing.gif");width:24px;height:24px;vertical-align:text-bottom;}.qq-upload-finished{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-retry,.qq-upload-delete{display:none;color:#000;}.qq-upload-cancel,.qq-upload-delete{color:#000;}.qq-upload-retryable .qq-upload-retry{display:inline;}.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-delete{font-size:12px;font-weight:normal;}.qq-upload-failed-text{display:none;font-style:italic;font-weight:bold;}.qq-upload-failed-icon{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-fail .qq-upload-failed-text{display:inline;}.qq-upload-retrying .qq-upload-failed-text{display:inline;color:#D60000;}.qq-upload-list li.qq-upload-success{background-color:#5DA30C;color:#FFF;}.qq-upload-list li.qq-upload-fail{background-color:#D60000;color:#FFF;}.qq-progress-bar{background:-moz-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,rgba(30,87,153,1)),color-stop(50%,rgba(41,137,216,1)),color-stop(51%,rgba(32,124,202,1)),color-stop(100%,rgba(125,185,232,1)));background:-webkit-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-o-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-ms-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:linear-gradient(to bottom,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);width:0;height:15px;border-radius:6px;margin-bottom:3px;display:none;}
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.3.1/fineuploader.min.js b/ajax/libs/file-uploader/3.3.1/fineuploader.min.js
new file mode 100644
index 000000000..2a50522e2
--- /dev/null
+++ b/ajax/libs/file-uploader/3.3.1/fineuploader.min.js
@@ -0,0 +1,13 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!==null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable!==null&&variable&&typeof variable==="object"&&variable.constructor===Object};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isString=function(maybeString){"use strict";return Object.prototype.toString.call(maybeString)==="[object String]"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFileOrInput=function(maybeFileOrInput){"use strict";if(qq.isBlob(maybeFileOrInput)&&window.File&&maybeFileOrInput instanceof File){return true}else if(window.HTMLInputElement){if(maybeFileOrInput instanceof HTMLInputElement){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}else if(maybeFileOrInput.tagName){if(maybeFileOrInput.tagName.toLowerCase()==="input"){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&maybeBlob instanceof Blob};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice||File.prototype.webkitSlice||File.prototype.mozSlice)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}})};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"misc_data",paramNames:{name:"qqblobname"}},paste:{targetElement:null,defaultName:"pasted_image"}};qq.extend(this._options,o,true);this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._netFilesUploadedOrQueued=0;this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}if(this._options.paste.targetElement){this._pasteHandler=this._createPasteHandler()}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._netFilesUploadedOrQueued++;this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset();this._pasteHandler.reset();this._netFilesUploadedOrQueued=0},addFiles:function(filesBlobDataOrInputs){var self=this,verifiedFilesOrInputs=[],index,fileOrInput;if(filesBlobDataOrInputs){if(!window.FileList||!(filesBlobDataOrInputs instanceof FileList)){filesBlobDataOrInputs=[].concat(filesBlobDataOrInputs)}for(index=0;index=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||this._options.cors.expected&&(qq.ie10()||!qq.ie()))},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)!==false){this._deleteHandler.sendDelete(id,this.getUuid(id))}}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(fileId){},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);if(isError){this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status,xhr)}else{this._netFilesUploadedOrQueued--;this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){},_onInputChange:function(input){if(qq.isXhrUploadSupported()){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0&&this._netFilesUploadedOrQueued+1>itemLimit){this._itemError("retryFailTooManyItems","");return false}this.log("Retrying upload for '"+fileName+"' (id: "+id+")...");this._filesInProgress.push(id);return true}else{this.log("'"+id+"' is not a valid file ID","error");return false}},_maybeParseAndSendUploadError:function(id,name,response,xhr){if(!response.success){if(xhr&&xhr.status!==200&&!response.error){this._options.callbacks.onError(id,name,"XHR returned response code "+xhr.status,xhr)}else{var errorReason=response.error?response.error:"Upload failure reason unknown";this._options.callbacks.onError(id,name,errorReason,xhr)}}},_uploadFileOrBlobDataList:function(fileOrBlobDataList){var index,validationDescriptors=this._getValidationDescriptors(fileOrBlobDataList),batchValid=this._isBatchValid(validationDescriptors);if(batchValid){if(fileOrBlobDataList.length>0){for(index=0;indexthis._options.validation.sizeLimit){this._itemError("sizeError",name);return false}else if(size&&size99);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,dirPending,droppedFiles=[],droppedEntriesCount=0,droppedEntriesParsedCount=0,disposeSupport=new qq.DisposeSupport;options={dropArea:null,extraDropzones:[],hideDropzones:true,multiple:true,classes:{dropActive:null},callbacks:{dropProcessing:function(isProcessing,files){},error:function(code,filename){},log:function(message,level){}}};
+qq.extend(options,o);function maybeUploadDroppedFiles(){if(droppedEntriesCount===droppedEntriesParsedCount&&!dirPending){options.callbacks.log("Grabbed "+droppedFiles.length+" files after tree traversal.");dz.dropDisabled(false);options.callbacks.dropProcessing(false,droppedFiles)}}function addDroppedFile(file){droppedFiles.push(file);droppedEntriesParsedCount+=1;maybeUploadDroppedFiles()}function traverseFileTree(entry){var dirReader,i;droppedEntriesCount+=1;if(entry.isFile){entry.file(function(file){addDroppedFile(file)})}else if(entry.isDirectory){dirPending=true;dirReader=entry.createReader();dirReader.readEntries(function(entries){droppedEntriesParsedCount+=1;for(i=0;i1&&!options.multiple){options.callbacks.dropProcessing(false);options.callbacks.error("tooManyFilesError","");dz.dropDisabled(false)}else{droppedFiles=[];droppedEntriesCount=0;droppedEntriesParsedCount=0;if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",dropDisabled:"qq-upload-drop-area-disabled",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file"},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false},paste:{promptForName:false,namePromptMessage:"Please name this image"},showMessage:function(message){setTimeout(function(){window.alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=window.confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)},showPrompt:function(message,defaultValue){var promise=new qq.Promise,retVal=window.prompt(message,defaultValue);if(retVal!=null&&qq.trimStr(retVal).length>0){promise.success(retVal)}else{promise.failure("Undefined or invalid user-supplied value.")}return promise}},true);qq.extend(this._options,o,true);this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop();if(this._options.paste.targetElement&&this._options.paste.promptForName){this._setupPastePrompt()}};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeExtraDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop()},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dnd,preventSelectFiles,defaultDropAreaEl;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){defaultDropAreaEl=this._find(this._options.element,"drop")}dnd=new qq.DragAndDrop({dropArea:defaultDropAreaEl,extraDropzones:this._options.dragAndDrop.extraDropzones,hideDropzones:this._options.dragAndDrop.hideDropzones,multiple:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{dropProcessing:function(isProcessing,files){var input=self._button.getInput();if(isProcessing){qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)}else{qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles)}if(files){self.addFiles(files)}},error:function(code,filename){self._itemError(code,filename)},log:function(message,level){self.log(message,level)}}});dnd.setup();return dnd},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){var item=this.getItemByFileId(id);if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}else{qq(item).addClass(this._classes.retryable);return false}},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)!==false){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}}}else{this.log("Delete request ignored for file ID "+id+", delete feature is disabled.","warn");return false}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}this._listElement.appendChild(item);if(this._options.display.fileSizeOnSubmit&&qq.isXhrUploadSupported()){this._displayFileSize(id)}},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=target=target.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_itemError:function(code,name){var message=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(message)},_batchError:function(message){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments);this._options.showMessage(message)},_setupPastePrompt:function(){var self=this;this._options.callbacks.onPasteReceived=function(){var message=self._options.paste.namePromptMessage,defaultVal=self._options.paste.defaultName;return self._options.showPrompt(message,defaultVal)}}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,dequeue,handlerImpl;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){}};qq.extend(options,o);log=options.log;dequeue=function(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){log("Cancelling "+id);options.paramsStore.remove(id);handlerImpl.cancel(id);dequeue(id)},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},getQueue:function(){return queue},reset:function(){log("Resetting upload handler");queue=[];handlerImpl.reset()},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}}};qq.UploadHandlerForm=function(o,uploadCompleteCallback,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var id=iframe.id;onloadCallbacks[uuids[id]]=callback;detachLoadEvents[id]=qq(iframe).attach("load",function(){if(inputs[id]){log("Received iframe load event for CORS upload request (file id "+id+")");postMessageCallbackTimers[id]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for file id "+id;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(id,function(message){log("Received the following window message: '"+message+"'");var response=qq.parseJson(message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];detachLoadEvent(id);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(id);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHTML=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHTML);if(innerHTML&&innerHTML.match(/^ ');iframe.setAttribute("id",id);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){if(api.isValid(id)){return inputs[id].value.replace(/.*(\/|\\)/,"")}else{log(id+" is not a valid item ID.","error")}},isValid:function(id){return inputs[id]!==undefined},reset:function(){inputs=[];uuids=[];detachLoadEvents={}},getUuid:function(id){return uuids[id]},cancel:function(id){options.onCancel(id,this.getName(id));delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(id);if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,this.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove();return id}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,logCallback){"use strict";
+var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.isFileChunkingSupported(),resumeEnabled=options.resume.enabled&&chunkFiles&&qq.areCookiesEnabled(),resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);delete fileState[id].xhr;uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(xhr){var response;try{response=qq.parseJson(xhr.responseText)}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function handleFileChunkingUpload(id,retry){var name=api.getName(id),firstChunkIndex=0,persistedChunkInfoForResume,firstChunkDataForResume,currentChunkIndex;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part);if(options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume))!==false){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].uuid=persistedChunkInfoForResume.uuid;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex)}}}for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}}uploadNextChunk(id)}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}api={add:function(fileOrBlobData){var id;if(fileOrBlobData instanceof File){id=fileState.push({file:fileOrBlobData})-1}else if(fileOrBlobData.blob instanceof Blob){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}fileState[id].uuid=qq.getUniqueId();return id},getName:function(id){if(api.isValid(id)){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}}else{log(id+" is not a valid item ID.","error")}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},getLoaded:function(id){return fileState[id].loaded||0},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}},cancel:function(id){var xhr=fileState[id].xhr;options.onCancel(id,this.getName(id));if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.3.1/iframe.xss.response.js b/ajax/libs/file-uploader/3.3.1/iframe.xss.response.js
new file mode 100644
index 000000000..e11fca1e5
--- /dev/null
+++ b/ajax/libs/file-uploader/3.3.1/iframe.xss.response.js
@@ -0,0 +1,6 @@
+(function() {
+ var match = /(\{.+\}).+/.exec(document.body.innerHTML);
+ if (match) {
+ parent.postMessage(match[1], '*');
+ }
+}());
diff --git a/ajax/libs/file-uploader/3.3.1/iframe.xss.response.min.js b/ajax/libs/file-uploader/3.3.1/iframe.xss.response.min.js
new file mode 100644
index 000000000..345cf3603
--- /dev/null
+++ b/ajax/libs/file-uploader/3.3.1/iframe.xss.response.min.js
@@ -0,0 +1 @@
+!function(){var match=/(\{.+\}).+/.exec(document.body.innerHTML);if(match){parent.postMessage(match[1],"*")}}();
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.3.1/loading.gif b/ajax/libs/file-uploader/3.3.1/loading.gif
new file mode 100644
index 000000000..6fba77609
Binary files /dev/null and b/ajax/libs/file-uploader/3.3.1/loading.gif differ
diff --git a/ajax/libs/file-uploader/3.3.1/processing.gif b/ajax/libs/file-uploader/3.3.1/processing.gif
new file mode 100644
index 000000000..7c99504e1
Binary files /dev/null and b/ajax/libs/file-uploader/3.3.1/processing.gif differ
diff --git a/ajax/libs/file-uploader/3.4.0/fineuploader-jquery.js b/ajax/libs/file-uploader/3.4.0/fineuploader-jquery.js
new file mode 100644
index 000000000..87eb992e8
--- /dev/null
+++ b/ajax/libs/file-uploader/3.4.0/fineuploader-jquery.js
@@ -0,0 +1,4214 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity !== null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+ if (qq.isBlob(maybeFileOrInput) && window.File && maybeFileOrInput instanceof File) {
+ return true;
+ }
+ else if (window.HTMLInputElement) {
+ if (maybeFileOrInput instanceof HTMLInputElement) {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ else if (maybeFileOrInput.tagName) {
+ if (maybeFileOrInput.tagName.toLowerCase() === 'input') {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && maybeBlob instanceof Blob;
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice || File.prototype.webkitSlice || File.prototype.mozSlice);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(obj, callback) {
+ "use strict";
+ var key, retVal;
+ if (obj) {
+ for (key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ retVal = callback(key, obj[key]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ c;
+
+ for(var i=0;i < ca.length;i++) {
+ c = ca[i];
+ while (c.charAt(0)==' ') {
+ c = c.substring(1,c.length);
+ }
+ if (c.indexOf(nameEQ) === 0) {
+ return c.substring(nameEQ.length,c.length);
+ }
+ }
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallback, failureCallback,
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ successCallback = onSuccess;
+ failureCallback = onFailure;
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallback) {
+ successCallback(val);
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallback) {
+ failureCallback(val);
+ }
+
+ return this;
+ }
+ };
+};/*globals qq*/
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ disposeSupport = new qq.DisposeSupport(),
+ options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input) {},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+
+ qq.extend(options, o);
+
+ // make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};qq.FineUploaderBasic = function(o){
+ var that = this;
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){},
+ onPasteReceived: function(blob) {
+ return new qq.Promise().success();
+ }
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'misc_data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ }
+ };
+
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._netFilesUploadedOrQueued = 0;
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function(){
+ return this._filesInProgress.length;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netFilesUploadedOrQueued++;
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._pasteHandler.reset();
+ this._netFilesUploadedOrQueued = 0;
+ },
+ addFiles: function(filesBlobDataOrInputs) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ index, fileOrInput;
+
+ if (filesBlobDataOrInputs) {
+ if (!window.FileList || !(filesBlobDataOrInputs instanceof FileList)) {
+ filesBlobDataOrInputs = [].concat(filesBlobDataOrInputs);
+ }
+
+ for (index = 0; index < filesBlobDataOrInputs.length; index+=1) {
+ fileOrInput = filesBlobDataOrInputs[index];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
+ this._uploadFileOrBlobDataList(verifiedFilesOrInputs);
+ }
+ },
+ addBlobs: function(blobDataOrArray) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._uploadFileOrBlobDataList(verifiedBlobDataList);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getPromissoryCallbackNames: function() {
+ return ["onPasteReceived"];
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.isXhrUploadSupported(),
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result);
+ },
+ onCancel: function(id, name){
+ self._onCancel(id, name);
+ self._options.callbacks.onCancel(id, name);
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ var pasteReceivedCallback = self._options.callbacks.onPasteReceived,
+ promise = pasteReceivedCallback(blob);
+
+ if (promise.then) {
+ promise.then(function(successData) {
+ self._handlePasteSuccess(blob, successData);
+ }, function(failureData) {
+ self.log("Ignoring pasted image per paste received callback. Reason = '" + failureData + "'");
+ });
+ }
+ else {
+ self.log("Promise contract not fulfilled in pasteReceived callback handler! Ignoring pasted item.", "error");
+ }
+ }
+ }
+ });
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netFilesUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total){
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._netFilesUploadedOrQueued--;
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name){
+ this._netFilesUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected ||
+ (this._options.cors.expected && (qq.ie10() || !qq.ie()))
+ )
+ );
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id) !== false) {
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ }
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(fileId) {},
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
+ }
+ else {
+ this._netFilesUploadedOrQueued--;
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name){},
+ _onInputChange: function(input){
+ if (qq.isXhrUploadSupported()){
+ this.addFiles(input.files);
+ } else {
+ this.addFiles(input);
+ }
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netFilesUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems", "");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : "Upload failure reason unknown";
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _uploadFileOrBlobDataList: function(fileOrBlobDataList){
+ var index,
+ validationDescriptors = this._getValidationDescriptors(fileOrBlobDataList),
+ batchValid = this._isBatchValid(validationDescriptors);
+
+ if (batchValid) {
+ if (fileOrBlobDataList.length > 0) {
+ for (index = 0; index < fileOrBlobDataList.length; index++){
+ if (this._validateFileOrBlobData(fileOrBlobDataList[index])){
+ this._upload(fileOrBlobDataList[index]);
+ } else {
+ if (this._options.validation.stopOnFirstInvalidFile){
+ return;
+ }
+ }
+ }
+ }
+ else {
+ this._itemError("noFilesError", "");
+ }
+ }
+ },
+ _upload: function(blobOrFileContainer){
+ var id = this._handler.add(blobOrFileContainer);
+ var name = this._handler.getName(id);
+
+ if (this._options.callbacks.onSubmit(id, name) !== false) {
+ this._onSubmit(id, name);
+ this._options.callbacks.onSubmitted(id, name);
+
+ if (this._options.autoUpload) {
+ this._handler.upload(id);
+ }
+ else {
+ this._storeForLater(id);
+ }
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _isBatchValid: function(validationDescriptors) {
+ //first, defer the check to the callback (ask the integrator)
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netFilesUploadedOrQueued + validationDescriptors.length,
+ batchValid = this._options.callbacks.onValidateBatch(validationDescriptors) !== false;
+
+ //if the callback hasn't rejected the batch, run some internal tests on the batch next
+ if (batchValid) {
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ batchValid = true;
+ }
+ else {
+ batchValid = false;
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ }
+
+ return batchValid;
+ },
+ _validateFileOrBlobData: function(fileOrBlobData){
+ var validationDescriptor, name, size;
+
+ validationDescriptor = this._getValidationDescriptor(fileOrBlobData);
+ name = validationDescriptor.name;
+ size = validationDescriptor.size;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ return false;
+ }
+
+ if (qq.isFileOrInput(fileOrBlobData) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ return false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ return false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ return false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ return false;
+ }
+
+ return true;
+ },
+ _itemError: function(code, name) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ extensionsForMessage;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ this._options.callbacks.onError(null, name, message);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1024;
+ i++;
+ } while (bytes > 99);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz, dirPending,
+ droppedFiles = [],
+ droppedEntriesCount = 0,
+ droppedEntriesParsedCount = 0,
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropArea: null,
+ extraDropzones: [],
+ hideDropzones: true,
+ multiple: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {},
+ error: function(code, filename) {},
+ log: function(message, level) {}
+ }
+ };
+
+ qq.extend(options, o);
+
+ function maybeUploadDroppedFiles() {
+ if (droppedEntriesCount === droppedEntriesParsedCount && !dirPending) {
+ options.callbacks.log('Grabbed ' + droppedFiles.length + " files after tree traversal.");
+ dz.dropDisabled(false);
+ options.callbacks.dropProcessing(false, droppedFiles);
+ }
+ }
+ function addDroppedFile(file) {
+ droppedFiles.push(file);
+ droppedEntriesParsedCount+=1;
+ maybeUploadDroppedFiles();
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i;
+
+ droppedEntriesCount+=1;
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ addDroppedFile(file);
+ });
+ }
+ else if (entry.isDirectory) {
+ dirPending = true;
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ droppedEntriesParsedCount+=1;
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]);
+ }
+
+ dirPending = false;
+
+ if (!entries.length) {
+ maybeUploadDroppedFiles();
+ }
+ });
+ }
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry;
+
+ options.callbacks.dropProcessing(true);
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.multiple) {
+ options.callbacks.dropProcessing(false);
+ options.callbacks.error('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ }
+ else {
+ droppedFiles = [];
+ droppedEntriesCount = 0;
+ droppedEntriesParsedCount = 0;
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ if (i === items.length-1) {
+ maybeUploadDroppedFiles();
+ }
+ }
+
+ else {
+ traverseFileTree(entry);
+ }
+ }
+ }
+ }
+ else {
+ options.callbacks.dropProcessing(false, dataTransfer.files);
+ dz.dropDisabled(false);
+ }
+ }
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer);
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ if (options.dropArea) {
+ options.extraDropzones.push(options.dropArea);
+ }
+
+ var i, dropzones = options.extraDropzones;
+
+ for (i=0; i < dropzones.length; i+=1){
+ setupDropzone(dropzones[i]);
+ }
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (options.dropArea && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ if (qq(options.dropArea).hasClass(options.classes.dropDisabled)) {
+ return;
+ }
+
+ options.dropArea.style.display = 'block';
+ for (i=0; i < dropzones.length; i+=1) {
+ dropzones[i].style.display = 'block';
+ }
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropzones && qq.FineUploader.prototype._leaving_document_out(e)) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropzones) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setup: function() {
+ setupDragDrop();
+ },
+
+ setupExtraDropzone: function(element) {
+ options.extraDropzones.push(element);
+ setupDropzone(element);
+ },
+
+ removeExtraDropzone: function(element) {
+ var i, dzs = options.extraDropzones;
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ dropDisabled: 'qq-upload-drop-area-disabled',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file"
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeExtraDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dnd, preventSelectFiles, defaultDropAreaEl;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ defaultDropAreaEl = this._find(this._options.element, 'drop');
+ }
+
+ dnd = new qq.DragAndDrop({
+ dropArea: defaultDropAreaEl,
+ extraDropzones: this._options.dragAndDrop.extraDropzones,
+ hideDropzones: this._options.dragAndDrop.hideDropzones,
+ multiple: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {
+ var input = self._button.getInput();
+
+ if (isProcessing) {
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ }
+ else {
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+ }
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ error: function(code, filename) {
+ self._itemError(code, filename);
+ },
+ log: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+
+ dnd.setup();
+
+ return dnd;
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type){
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name){
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id) !== false) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ }
+ }
+ else {
+ this.log("Delete request ignored for file ID " + id + ", delete feature is disabled.", "warn");
+ return false;
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ this._listElement.appendChild(item);
+
+ if (this._options.display.fileSizeOnSubmit && qq.isXhrUploadSupported()) {
+ this._displayFileSize(id);
+ }
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = target = target.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, dequeue, handlerImpl;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ dequeue = function(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.isXhrUploadSupported()) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, log);
+ }
+
+
+ return {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ return handlerImpl.upload(id);
+ }
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ handlerImpl.cancel(id);
+ dequeue(id);
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ /**
+ * Returns id of files being uploaded or
+ * waiting for their turn
+ */
+ getQueue: function(){
+ return queue;
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ queue = [];
+ handlerImpl.reset();
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var id = iframe.id;
+
+ onloadCallbacks[uuids[id]] = callback;
+
+ detachLoadEvents[id] = qq(iframe).attach('load', function() {
+ if (inputs[id]) {
+ log("Received iframe load event for CORS upload request (file id " + id + ")");
+
+ postMessageCallbackTimers[id] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for file id " + id;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(id, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = qq.parseJson(message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+
+ detachLoadEvent(id);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(id);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHTML = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHTML);
+ //plain text response may be wrapped in tag
+ if (innerHTML && innerHTML.match(/^ ');
+
+ iframe.setAttribute('id', id);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ options.onCancel(id, this.getName(id));
+
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(id);
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ },
+ upload: function(id){
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, this.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+
+ return id;
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.isFileChunkingSupported(),
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.areCookiesEnabled(),
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+
+ options.onComplete(id, name, response, xhr);
+ delete fileState[id].xhr;
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var name = api.getName(id),
+ firstChunkIndex = 0,
+ persistedChunkInfoForResume, firstChunkDataForResume, currentChunkIndex;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part);
+ if (options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume)) !== false) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].uuid = persistedChunkInfoForResume.uuid;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+ }
+ }
+ }
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id;
+
+ if (fileOrBlobData instanceof File) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (fileOrBlobData.blob instanceof Blob) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ fileState[id].uuid = qq.getUniqueId();
+ return id;
+ },
+ getName: function(id){
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ /**
+ * Returns uploaded bytes for file identified by id
+ */
+ getLoaded: function(id){
+ return fileState[id].loaded || 0;
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry){
+ var name = this.getName(id);
+
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ },
+ cancel: function(id){
+ var xhr = fileState[id].xhr;
+
+ options.onCancel(id, this.getName(id));
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var uploader, $el, init, dataStore, pluginOption, pluginOptions, addCallbacks, transformVariables, isValidCommand,
+ delegateCommand;
+
+ pluginOptions = ['uploaderType'];
+
+ init = function (options) {
+ if (options) {
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+
+ if (pluginOption('uploaderType') === 'basic') {
+ uploader(new qq.FineUploaderBasic(xformedOpts));
+ }
+ else {
+ uploader(new qq.FineUploader(xformedOpts));
+ }
+ }
+
+ return $el;
+ };
+
+ dataStore = function(key, val) {
+ var data = $el.data('fineuploader');
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data('fineuploader', data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ //the underlying Fine Uploader instance is stored in jQuery's data stored, associated with the element
+ // tied to this instance of the plug-in
+ uploader = function(instanceToStore) {
+ return dataStore('uploader', instanceToStore);
+ };
+
+ pluginOption = function(option, optionVal) {
+ return dataStore(option, optionVal);
+ };
+
+ //implement all callbacks defined in Fine Uploader as functions that trigger appropriately names events and
+ // return the result of executing the bound handler back to Fine Uploader
+ addCallbacks = function(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {},
+ uploaderInst = new qq.FineUploaderBasic();
+
+ $.each(uploaderInst._options.callbacks, function(prop, func) {
+ var name, $callbackEl;
+
+ name = /^on(\w+)/.exec(prop)[1];
+ name = name.substring(0, 1).toLowerCase() + name.substring(1);
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var origFunc = func,
+ args = Array.prototype.slice.call(arguments),
+ jqueryHandlerResult = $callbackEl.triggerHandler(name, args);
+
+ if (jqueryHandlerResult === undefined &&
+ $.inArray(prop, uploaderInst.getPromissoryCallbackNames()) >= 0) {
+ return origFunc();
+ }
+
+ return jqueryHandlerResult;
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ transformVariables = function(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ if (source.uploaderType !== 'basic') {
+ xformed = { element : $el[0] };
+ }
+ else {
+ xformed = {};
+ }
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if ($.inArray(prop, pluginOptions) >= 0) {
+ pluginOption(prop, val);
+ }
+ else if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ isValidCommand = function(command) {
+ return $.type(command) === "string" &&
+ !command.match(/^_/) && //enforce private methods convention
+ uploader()[command] !== undefined;
+ };
+
+ //assuming we have already verified that this is a valid command, call the associated function in the underlying
+ // Fine Uploader instance (passing along the arguments from the caller) and return the result of the call back to the caller
+ delegateCommand = function(command) {
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
+
+ transformVariables(origArgs, xformedArgs);
+
+ return uploader()[command].apply(uploader(), xformedArgs);
+ };
+
+ $.fn.fineUploader = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (uploader() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error('Method ' + optionsOrCommand + ' does not exist on jQuery.fineUploader');
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
diff --git a/ajax/libs/file-uploader/3.4.0/fineuploader-jquery.min.js b/ajax/libs/file-uploader/3.4.0/fineuploader-jquery.min.js
new file mode 100644
index 000000000..039415384
--- /dev/null
+++ b/ajax/libs/file-uploader/3.4.0/fineuploader-jquery.min.js
@@ -0,0 +1,13 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!==null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable!==null&&variable&&typeof variable==="object"&&variable.constructor===Object};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isString=function(maybeString){"use strict";return Object.prototype.toString.call(maybeString)==="[object String]"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFileOrInput=function(maybeFileOrInput){"use strict";if(qq.isBlob(maybeFileOrInput)&&window.File&&maybeFileOrInput instanceof File){return true}else if(window.HTMLInputElement){if(maybeFileOrInput instanceof HTMLInputElement){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}else if(maybeFileOrInput.tagName){if(maybeFileOrInput.tagName.toLowerCase()==="input"){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&maybeBlob instanceof Blob};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice||File.prototype.webkitSlice||File.prototype.mozSlice)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}})};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"misc_data",paramNames:{name:"qqblobname"}},paste:{targetElement:null,defaultName:"pasted_image"}};qq.extend(this._options,o,true);this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._netFilesUploadedOrQueued=0;this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}if(this._options.paste.targetElement){this._pasteHandler=this._createPasteHandler()}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._netFilesUploadedOrQueued++;this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset();this._pasteHandler.reset();this._netFilesUploadedOrQueued=0},addFiles:function(filesBlobDataOrInputs){var self=this,verifiedFilesOrInputs=[],index,fileOrInput;if(filesBlobDataOrInputs){if(!window.FileList||!(filesBlobDataOrInputs instanceof FileList)){filesBlobDataOrInputs=[].concat(filesBlobDataOrInputs)}for(index=0;index=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||this._options.cors.expected&&(qq.ie10()||!qq.ie()))},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)!==false){this._deleteHandler.sendDelete(id,this.getUuid(id))}}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(fileId){},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);if(isError){this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status,xhr)}else{this._netFilesUploadedOrQueued--;this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){},_onInputChange:function(input){if(qq.isXhrUploadSupported()){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0&&this._netFilesUploadedOrQueued+1>itemLimit){this._itemError("retryFailTooManyItems","");return false}this.log("Retrying upload for '"+fileName+"' (id: "+id+")...");this._filesInProgress.push(id);return true}else{this.log("'"+id+"' is not a valid file ID","error");return false}},_maybeParseAndSendUploadError:function(id,name,response,xhr){if(!response.success){if(xhr&&xhr.status!==200&&!response.error){this._options.callbacks.onError(id,name,"XHR returned response code "+xhr.status,xhr)}else{var errorReason=response.error?response.error:"Upload failure reason unknown";this._options.callbacks.onError(id,name,errorReason,xhr)}}},_uploadFileOrBlobDataList:function(fileOrBlobDataList){var index,validationDescriptors=this._getValidationDescriptors(fileOrBlobDataList),batchValid=this._isBatchValid(validationDescriptors);if(batchValid){if(fileOrBlobDataList.length>0){for(index=0;indexthis._options.validation.sizeLimit){this._itemError("sizeError",name);return false}else if(size&&size99);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,dirPending,droppedFiles=[],droppedEntriesCount=0,droppedEntriesParsedCount=0,disposeSupport=new qq.DisposeSupport;options={dropArea:null,extraDropzones:[],hideDropzones:true,multiple:true,classes:{dropActive:null},callbacks:{dropProcessing:function(isProcessing,files){},error:function(code,filename){},log:function(message,level){}}};
+qq.extend(options,o);function maybeUploadDroppedFiles(){if(droppedEntriesCount===droppedEntriesParsedCount&&!dirPending){options.callbacks.log("Grabbed "+droppedFiles.length+" files after tree traversal.");dz.dropDisabled(false);options.callbacks.dropProcessing(false,droppedFiles)}}function addDroppedFile(file){droppedFiles.push(file);droppedEntriesParsedCount+=1;maybeUploadDroppedFiles()}function traverseFileTree(entry){var dirReader,i;droppedEntriesCount+=1;if(entry.isFile){entry.file(function(file){addDroppedFile(file)})}else if(entry.isDirectory){dirPending=true;dirReader=entry.createReader();dirReader.readEntries(function(entries){droppedEntriesParsedCount+=1;for(i=0;i1&&!options.multiple){options.callbacks.dropProcessing(false);options.callbacks.error("tooManyFilesError","");dz.dropDisabled(false)}else{droppedFiles=[];droppedEntriesCount=0;droppedEntriesParsedCount=0;if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",dropDisabled:"qq-upload-drop-area-disabled",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file"},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false},paste:{promptForName:false,namePromptMessage:"Please name this image"},showMessage:function(message){setTimeout(function(){window.alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=window.confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)},showPrompt:function(message,defaultValue){var promise=new qq.Promise,retVal=window.prompt(message,defaultValue);if(retVal!=null&&qq.trimStr(retVal).length>0){promise.success(retVal)}else{promise.failure("Undefined or invalid user-supplied value.")}return promise}},true);qq.extend(this._options,o,true);this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop();if(this._options.paste.targetElement&&this._options.paste.promptForName){this._setupPastePrompt()}};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeExtraDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop()},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dnd,preventSelectFiles,defaultDropAreaEl;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){defaultDropAreaEl=this._find(this._options.element,"drop")}dnd=new qq.DragAndDrop({dropArea:defaultDropAreaEl,extraDropzones:this._options.dragAndDrop.extraDropzones,hideDropzones:this._options.dragAndDrop.hideDropzones,multiple:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{dropProcessing:function(isProcessing,files){var input=self._button.getInput();if(isProcessing){qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)}else{qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles)}if(files){self.addFiles(files)}},error:function(code,filename){self._itemError(code,filename)},log:function(message,level){self.log(message,level)}}});dnd.setup();return dnd},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){var item=this.getItemByFileId(id);if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}else{qq(item).addClass(this._classes.retryable);return false}},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)!==false){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}}}else{this.log("Delete request ignored for file ID "+id+", delete feature is disabled.","warn");return false}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}this._listElement.appendChild(item);if(this._options.display.fileSizeOnSubmit&&qq.isXhrUploadSupported()){this._displayFileSize(id)}},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=target=target.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_itemError:function(code,name){var message=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(message)},_batchError:function(message){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments);this._options.showMessage(message)},_setupPastePrompt:function(){var self=this;this._options.callbacks.onPasteReceived=function(){var message=self._options.paste.namePromptMessage,defaultVal=self._options.paste.defaultName;return self._options.showPrompt(message,defaultVal)}}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,dequeue,handlerImpl;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){}};qq.extend(options,o);log=options.log;dequeue=function(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){log("Cancelling "+id);options.paramsStore.remove(id);handlerImpl.cancel(id);dequeue(id)},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},getQueue:function(){return queue},reset:function(){log("Resetting upload handler");queue=[];handlerImpl.reset()},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}}};qq.UploadHandlerForm=function(o,uploadCompleteCallback,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var id=iframe.id;onloadCallbacks[uuids[id]]=callback;detachLoadEvents[id]=qq(iframe).attach("load",function(){if(inputs[id]){log("Received iframe load event for CORS upload request (file id "+id+")");postMessageCallbackTimers[id]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for file id "+id;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(id,function(message){log("Received the following window message: '"+message+"'");var response=qq.parseJson(message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];detachLoadEvent(id);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(id);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHTML=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHTML);if(innerHTML&&innerHTML.match(/^ ');iframe.setAttribute("id",id);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){if(api.isValid(id)){return inputs[id].value.replace(/.*(\/|\\)/,"")}else{log(id+" is not a valid item ID.","error")}},isValid:function(id){return inputs[id]!==undefined},reset:function(){inputs=[];uuids=[];detachLoadEvents={}},getUuid:function(id){return uuids[id]},cancel:function(id){options.onCancel(id,this.getName(id));delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(id);if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,this.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove();return id}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,logCallback){"use strict";
+var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.isFileChunkingSupported(),resumeEnabled=options.resume.enabled&&chunkFiles&&qq.areCookiesEnabled(),resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);delete fileState[id].xhr;uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(xhr){var response;try{response=qq.parseJson(xhr.responseText)}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function handleFileChunkingUpload(id,retry){var name=api.getName(id),firstChunkIndex=0,persistedChunkInfoForResume,firstChunkDataForResume,currentChunkIndex;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part);if(options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume))!==false){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].uuid=persistedChunkInfoForResume.uuid;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex)}}}for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}}uploadNextChunk(id)}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}api={add:function(fileOrBlobData){var id;if(fileOrBlobData instanceof File){id=fileState.push({file:fileOrBlobData})-1}else if(fileOrBlobData.blob instanceof Blob){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}fileState[id].uuid=qq.getUniqueId();return id},getName:function(id){if(api.isValid(id)){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}}else{log(id+" is not a valid item ID.","error")}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},getLoaded:function(id){return fileState[id].loaded||0},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}},cancel:function(id){var xhr=fileState[id].xhr;options.onCancel(id,this.getName(id));if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};!function($){"use strict";var uploader,$el,init,dataStore,pluginOption,pluginOptions,addCallbacks,transformVariables,isValidCommand,delegateCommand;pluginOptions=["uploaderType"];init=function(options){if(options){var xformedOpts=transformVariables(options);addCallbacks(xformedOpts);if(pluginOption("uploaderType")==="basic"){uploader(new qq.FineUploaderBasic(xformedOpts))}else{uploader(new qq.FineUploader(xformedOpts))}}return $el};dataStore=function(key,val){var data=$el.data("fineuploader");if(val){if(data===undefined){data={}}data[key]=val;$el.data("fineuploader",data)}else{if(data===undefined){return null}return data[key]}};uploader=function(instanceToStore){return dataStore("uploader",instanceToStore)};pluginOption=function(option,optionVal){return dataStore(option,optionVal)};addCallbacks=function(transformedOpts){var callbacks=transformedOpts.callbacks={},uploaderInst=new qq.FineUploaderBasic;$.each(uploaderInst._options.callbacks,function(prop,func){var name,$callbackEl;name=/^on(\w+)/.exec(prop)[1];name=name.substring(0,1).toLowerCase()+name.substring(1);$callbackEl=$el;callbacks[prop]=function(){var origFunc=func,args=Array.prototype.slice.call(arguments),jqueryHandlerResult=$callbackEl.triggerHandler(name,args);if(jqueryHandlerResult===undefined&&$.inArray(prop,uploaderInst.getPromissoryCallbackNames())>=0){return origFunc()}return jqueryHandlerResult}})};transformVariables=function(source,dest){var xformed,arrayVals;if(dest===undefined){if(source.uploaderType!=="basic"){xformed={element:$el[0]}}else{xformed={}}}else{xformed=dest}$.each(source,function(prop,val){if($.inArray(prop,pluginOptions)>=0){pluginOption(prop,val)}else if(val instanceof $){xformed[prop]=val[0]}else if($.isPlainObject(val)){xformed[prop]={};transformVariables(val,xformed[prop])}else if($.isArray(val)){arrayVals=[];$.each(val,function(idx,arrayVal){if(arrayVal instanceof $){$.merge(arrayVals,arrayVal)}else{arrayVals.push(arrayVal)}});xformed[prop]=arrayVals}else{xformed[prop]=val}});if(dest===undefined){return xformed}};isValidCommand=function(command){return $.type(command)==="string"&&!command.match(/^_/)&&uploader()[command]!==undefined};delegateCommand=function(command){var xformedArgs=[],origArgs=Array.prototype.slice.call(arguments,1);transformVariables(origArgs,xformedArgs);return uploader()[command].apply(uploader(),xformedArgs)};$.fn.fineUploader=function(optionsOrCommand){var self=this,selfArgs=arguments,retVals=[];this.each(function(index,el){$el=$(el);if(uploader()&&isValidCommand(optionsOrCommand)){retVals.push(delegateCommand.apply(self,selfArgs));if(self.length===1){return false}}else if(typeof optionsOrCommand==="object"||!optionsOrCommand){init.apply(self,selfArgs)}else{$.error("Method "+optionsOrCommand+" does not exist on jQuery.fineUploader")}});if(retVals.length===1){return retVals[0]}else if(retVals.length>1){return retVals}return this}}(jQuery);
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.4.0/fineuploader.css b/ajax/libs/file-uploader/3.4.0/fineuploader.css
new file mode 100644
index 000000000..11a436a06
--- /dev/null
+++ b/ajax/libs/file-uploader/3.4.0/fineuploader.css
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2013, Widen Enterprises info@fineuploader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+.qq-uploader {
+ position: relative;
+ width: 100%;
+}
+.qq-upload-button {
+ display: block;
+ width: 105px;
+ padding: 7px 0;
+ text-align: center;
+ background: #880000;
+ border-bottom: 1px solid #DDD;
+ color: #FFF;
+}
+.qq-upload-button-hover {
+ background: #CC0000;
+}
+.qq-upload-button-focus {
+ outline: 1px dotted #000000;
+}
+.qq-upload-drop-area, .qq-upload-extra-drop-area {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ min-height: 30px;
+ z-index: 2;
+ background: #FF9797;
+ text-align: center;
+}
+.qq-upload-drop-area span {
+ display: block;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ margin-top: -8px;
+ font-size: 16px;
+}
+.qq-upload-extra-drop-area {
+ position: relative;
+ margin-top: 50px;
+ font-size: 16px;
+ padding-top: 30px;
+ height: 20px;
+ min-height: 40px;
+}
+.qq-upload-drop-area-active {
+ background: #FF7171;
+}
+.qq-upload-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+.qq-upload-list li {
+ margin: 0;
+ padding: 9px;
+ line-height: 15px;
+ font-size: 16px;
+ background-color: #FFF0BD;
+}
+.qq-upload-file, .qq-upload-spinner, .qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text, .qq-upload-finished, .qq-upload-delete {
+ margin-right: 12px;
+}
+.qq-upload-file {
+}
+.qq-upload-spinner {
+ display: inline-block;
+ background: url("loading.gif");
+ width: 15px;
+ height: 15px;
+ vertical-align: text-bottom;
+}
+.qq-drop-processing {
+ display: none;
+}
+.qq-drop-processing-spinner {
+ display: inline-block;
+ background: url("processing.gif");
+ width: 24px;
+ height: 24px;
+ vertical-align: text-bottom;
+}
+.qq-upload-finished {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-retry, .qq-upload-delete {
+ display: none;
+ color: #000000;
+}
+.qq-upload-cancel, .qq-upload-delete {
+ color: #000000;
+}
+.qq-upload-retryable .qq-upload-retry {
+ display: inline;
+}
+.qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-delete {
+ font-size: 12px;
+ font-weight: normal;
+}
+.qq-upload-failed-text {
+ display: none;
+ font-style: italic;
+ font-weight: bold;
+}
+.qq-upload-failed-icon {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-fail .qq-upload-failed-text {
+ display: inline;
+}
+.qq-upload-retrying .qq-upload-failed-text {
+ display: inline;
+ color: #D60000;
+}
+.qq-upload-list li.qq-upload-success {
+ background-color: #5DA30C;
+ color: #FFFFFF;
+}
+.qq-upload-list li.qq-upload-fail {
+ background-color: #D60000;
+ color: #FFFFFF;
+}
+.qq-progress-bar {
+ background: -moz-linear-gradient(top, rgba(30,87,153,1) 0%, rgba(41,137,216,1) 50%, rgba(32,124,202,1) 51%, rgba(125,185,232,1) 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(30,87,153,1)), color-stop(50%,rgba(41,137,216,1)), color-stop(51%,rgba(32,124,202,1)), color-stop(100%,rgba(125,185,232,1))); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* IE10+ */
+ background: linear-gradient(to bottom, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* W3C */
+ width: 0%;
+ height: 15px;
+ border-radius: 6px;
+ margin-bottom: 3px;
+ display: none;
+}
diff --git a/ajax/libs/file-uploader/3.4.0/fineuploader.js b/ajax/libs/file-uploader/3.4.0/fineuploader.js
new file mode 100644
index 000000000..d9dc9c79c
--- /dev/null
+++ b/ajax/libs/file-uploader/3.4.0/fineuploader.js
@@ -0,0 +1,4037 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity !== null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+ if (qq.isBlob(maybeFileOrInput) && window.File && maybeFileOrInput instanceof File) {
+ return true;
+ }
+ else if (window.HTMLInputElement) {
+ if (maybeFileOrInput instanceof HTMLInputElement) {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ else if (maybeFileOrInput.tagName) {
+ if (maybeFileOrInput.tagName.toLowerCase() === 'input') {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && maybeBlob instanceof Blob;
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice || File.prototype.webkitSlice || File.prototype.mozSlice);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(obj, callback) {
+ "use strict";
+ var key, retVal;
+ if (obj) {
+ for (key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ retVal = callback(key, obj[key]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ c;
+
+ for(var i=0;i < ca.length;i++) {
+ c = ca[i];
+ while (c.charAt(0)==' ') {
+ c = c.substring(1,c.length);
+ }
+ if (c.indexOf(nameEQ) === 0) {
+ return c.substring(nameEQ.length,c.length);
+ }
+ }
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallback, failureCallback,
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ successCallback = onSuccess;
+ failureCallback = onFailure;
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallback) {
+ successCallback(val);
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallback) {
+ failureCallback(val);
+ }
+
+ return this;
+ }
+ };
+};/*globals qq*/
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ disposeSupport = new qq.DisposeSupport(),
+ options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input) {},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+
+ qq.extend(options, o);
+
+ // make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};qq.FineUploaderBasic = function(o){
+ var that = this;
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){},
+ onPasteReceived: function(blob) {
+ return new qq.Promise().success();
+ }
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'misc_data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ }
+ };
+
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._netFilesUploadedOrQueued = 0;
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function(){
+ return this._filesInProgress.length;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netFilesUploadedOrQueued++;
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._pasteHandler.reset();
+ this._netFilesUploadedOrQueued = 0;
+ },
+ addFiles: function(filesBlobDataOrInputs) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ index, fileOrInput;
+
+ if (filesBlobDataOrInputs) {
+ if (!window.FileList || !(filesBlobDataOrInputs instanceof FileList)) {
+ filesBlobDataOrInputs = [].concat(filesBlobDataOrInputs);
+ }
+
+ for (index = 0; index < filesBlobDataOrInputs.length; index+=1) {
+ fileOrInput = filesBlobDataOrInputs[index];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
+ this._uploadFileOrBlobDataList(verifiedFilesOrInputs);
+ }
+ },
+ addBlobs: function(blobDataOrArray) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._uploadFileOrBlobDataList(verifiedBlobDataList);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getPromissoryCallbackNames: function() {
+ return ["onPasteReceived"];
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.isXhrUploadSupported(),
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result);
+ },
+ onCancel: function(id, name){
+ self._onCancel(id, name);
+ self._options.callbacks.onCancel(id, name);
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ var pasteReceivedCallback = self._options.callbacks.onPasteReceived,
+ promise = pasteReceivedCallback(blob);
+
+ if (promise.then) {
+ promise.then(function(successData) {
+ self._handlePasteSuccess(blob, successData);
+ }, function(failureData) {
+ self.log("Ignoring pasted image per paste received callback. Reason = '" + failureData + "'");
+ });
+ }
+ else {
+ self.log("Promise contract not fulfilled in pasteReceived callback handler! Ignoring pasted item.", "error");
+ }
+ }
+ }
+ });
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netFilesUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total){
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._netFilesUploadedOrQueued--;
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name){
+ this._netFilesUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected ||
+ (this._options.cors.expected && (qq.ie10() || !qq.ie()))
+ )
+ );
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id) !== false) {
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ }
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(fileId) {},
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
+ }
+ else {
+ this._netFilesUploadedOrQueued--;
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name){},
+ _onInputChange: function(input){
+ if (qq.isXhrUploadSupported()){
+ this.addFiles(input.files);
+ } else {
+ this.addFiles(input);
+ }
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netFilesUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems", "");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : "Upload failure reason unknown";
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _uploadFileOrBlobDataList: function(fileOrBlobDataList){
+ var index,
+ validationDescriptors = this._getValidationDescriptors(fileOrBlobDataList),
+ batchValid = this._isBatchValid(validationDescriptors);
+
+ if (batchValid) {
+ if (fileOrBlobDataList.length > 0) {
+ for (index = 0; index < fileOrBlobDataList.length; index++){
+ if (this._validateFileOrBlobData(fileOrBlobDataList[index])){
+ this._upload(fileOrBlobDataList[index]);
+ } else {
+ if (this._options.validation.stopOnFirstInvalidFile){
+ return;
+ }
+ }
+ }
+ }
+ else {
+ this._itemError("noFilesError", "");
+ }
+ }
+ },
+ _upload: function(blobOrFileContainer){
+ var id = this._handler.add(blobOrFileContainer);
+ var name = this._handler.getName(id);
+
+ if (this._options.callbacks.onSubmit(id, name) !== false) {
+ this._onSubmit(id, name);
+ this._options.callbacks.onSubmitted(id, name);
+
+ if (this._options.autoUpload) {
+ this._handler.upload(id);
+ }
+ else {
+ this._storeForLater(id);
+ }
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _isBatchValid: function(validationDescriptors) {
+ //first, defer the check to the callback (ask the integrator)
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netFilesUploadedOrQueued + validationDescriptors.length,
+ batchValid = this._options.callbacks.onValidateBatch(validationDescriptors) !== false;
+
+ //if the callback hasn't rejected the batch, run some internal tests on the batch next
+ if (batchValid) {
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ batchValid = true;
+ }
+ else {
+ batchValid = false;
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ }
+
+ return batchValid;
+ },
+ _validateFileOrBlobData: function(fileOrBlobData){
+ var validationDescriptor, name, size;
+
+ validationDescriptor = this._getValidationDescriptor(fileOrBlobData);
+ name = validationDescriptor.name;
+ size = validationDescriptor.size;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ return false;
+ }
+
+ if (qq.isFileOrInput(fileOrBlobData) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ return false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ return false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ return false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ return false;
+ }
+
+ return true;
+ },
+ _itemError: function(code, name) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ extensionsForMessage;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ this._options.callbacks.onError(null, name, message);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1024;
+ i++;
+ } while (bytes > 99);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz, dirPending,
+ droppedFiles = [],
+ droppedEntriesCount = 0,
+ droppedEntriesParsedCount = 0,
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropArea: null,
+ extraDropzones: [],
+ hideDropzones: true,
+ multiple: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {},
+ error: function(code, filename) {},
+ log: function(message, level) {}
+ }
+ };
+
+ qq.extend(options, o);
+
+ function maybeUploadDroppedFiles() {
+ if (droppedEntriesCount === droppedEntriesParsedCount && !dirPending) {
+ options.callbacks.log('Grabbed ' + droppedFiles.length + " files after tree traversal.");
+ dz.dropDisabled(false);
+ options.callbacks.dropProcessing(false, droppedFiles);
+ }
+ }
+ function addDroppedFile(file) {
+ droppedFiles.push(file);
+ droppedEntriesParsedCount+=1;
+ maybeUploadDroppedFiles();
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i;
+
+ droppedEntriesCount+=1;
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ addDroppedFile(file);
+ });
+ }
+ else if (entry.isDirectory) {
+ dirPending = true;
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ droppedEntriesParsedCount+=1;
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]);
+ }
+
+ dirPending = false;
+
+ if (!entries.length) {
+ maybeUploadDroppedFiles();
+ }
+ });
+ }
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry;
+
+ options.callbacks.dropProcessing(true);
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.multiple) {
+ options.callbacks.dropProcessing(false);
+ options.callbacks.error('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ }
+ else {
+ droppedFiles = [];
+ droppedEntriesCount = 0;
+ droppedEntriesParsedCount = 0;
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ if (i === items.length-1) {
+ maybeUploadDroppedFiles();
+ }
+ }
+
+ else {
+ traverseFileTree(entry);
+ }
+ }
+ }
+ }
+ else {
+ options.callbacks.dropProcessing(false, dataTransfer.files);
+ dz.dropDisabled(false);
+ }
+ }
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer);
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ if (options.dropArea) {
+ options.extraDropzones.push(options.dropArea);
+ }
+
+ var i, dropzones = options.extraDropzones;
+
+ for (i=0; i < dropzones.length; i+=1){
+ setupDropzone(dropzones[i]);
+ }
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (options.dropArea && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ if (qq(options.dropArea).hasClass(options.classes.dropDisabled)) {
+ return;
+ }
+
+ options.dropArea.style.display = 'block';
+ for (i=0; i < dropzones.length; i+=1) {
+ dropzones[i].style.display = 'block';
+ }
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropzones && qq.FineUploader.prototype._leaving_document_out(e)) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropzones) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setup: function() {
+ setupDragDrop();
+ },
+
+ setupExtraDropzone: function(element) {
+ options.extraDropzones.push(element);
+ setupDropzone(element);
+ },
+
+ removeExtraDropzone: function(element) {
+ var i, dzs = options.extraDropzones;
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ dropDisabled: 'qq-upload-drop-area-disabled',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file"
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeExtraDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dnd, preventSelectFiles, defaultDropAreaEl;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ defaultDropAreaEl = this._find(this._options.element, 'drop');
+ }
+
+ dnd = new qq.DragAndDrop({
+ dropArea: defaultDropAreaEl,
+ extraDropzones: this._options.dragAndDrop.extraDropzones,
+ hideDropzones: this._options.dragAndDrop.hideDropzones,
+ multiple: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {
+ var input = self._button.getInput();
+
+ if (isProcessing) {
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ }
+ else {
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+ }
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ error: function(code, filename) {
+ self._itemError(code, filename);
+ },
+ log: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+
+ dnd.setup();
+
+ return dnd;
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type){
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name){
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id) !== false) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ }
+ }
+ else {
+ this.log("Delete request ignored for file ID " + id + ", delete feature is disabled.", "warn");
+ return false;
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ this._listElement.appendChild(item);
+
+ if (this._options.display.fileSizeOnSubmit && qq.isXhrUploadSupported()) {
+ this._displayFileSize(id);
+ }
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = target = target.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, dequeue, handlerImpl;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ dequeue = function(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.isXhrUploadSupported()) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, log);
+ }
+
+
+ return {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ return handlerImpl.upload(id);
+ }
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ handlerImpl.cancel(id);
+ dequeue(id);
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ /**
+ * Returns id of files being uploaded or
+ * waiting for their turn
+ */
+ getQueue: function(){
+ return queue;
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ queue = [];
+ handlerImpl.reset();
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var id = iframe.id;
+
+ onloadCallbacks[uuids[id]] = callback;
+
+ detachLoadEvents[id] = qq(iframe).attach('load', function() {
+ if (inputs[id]) {
+ log("Received iframe load event for CORS upload request (file id " + id + ")");
+
+ postMessageCallbackTimers[id] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for file id " + id;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(id, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = qq.parseJson(message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+
+ detachLoadEvent(id);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(id);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHTML = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHTML);
+ //plain text response may be wrapped in tag
+ if (innerHTML && innerHTML.match(/^ ');
+
+ iframe.setAttribute('id', id);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ options.onCancel(id, this.getName(id));
+
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(id);
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ },
+ upload: function(id){
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, this.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+
+ return id;
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.isFileChunkingSupported(),
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.areCookiesEnabled(),
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+
+ options.onComplete(id, name, response, xhr);
+ delete fileState[id].xhr;
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var name = api.getName(id),
+ firstChunkIndex = 0,
+ persistedChunkInfoForResume, firstChunkDataForResume, currentChunkIndex;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part);
+ if (options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume)) !== false) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].uuid = persistedChunkInfoForResume.uuid;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+ }
+ }
+ }
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id;
+
+ if (fileOrBlobData instanceof File) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (fileOrBlobData.blob instanceof Blob) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ fileState[id].uuid = qq.getUniqueId();
+ return id;
+ },
+ getName: function(id){
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ /**
+ * Returns uploaded bytes for file identified by id
+ */
+ getLoaded: function(id){
+ return fileState[id].loaded || 0;
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry){
+ var name = this.getName(id);
+
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ },
+ cancel: function(id){
+ var xhr = fileState[id].xhr;
+
+ options.onCancel(id, this.getName(id));
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
diff --git a/ajax/libs/file-uploader/3.4.0/fineuploader.min.css b/ajax/libs/file-uploader/3.4.0/fineuploader.min.css
new file mode 100644
index 000000000..110e8fa52
--- /dev/null
+++ b/ajax/libs/file-uploader/3.4.0/fineuploader.min.css
@@ -0,0 +1 @@
+.qq-uploader{position:relative;width:100%;}.qq-upload-button{display:block;width:105px;padding:7px 0;text-align:center;background:#800;border-bottom:1px solid #DDD;color:#FFF;}.qq-upload-button-hover{background:#C00;}.qq-upload-button-focus{outline:1px dotted #000;}.qq-upload-drop-area,.qq-upload-extra-drop-area{position:absolute;top:0;left:0;width:100%;height:100%;min-height:30px;z-index:2;background:#FF9797;text-align:center;}.qq-upload-drop-area span{display:block;position:absolute;top:50%;width:100%;margin-top:-8px;font-size:16px;}.qq-upload-extra-drop-area{position:relative;margin-top:50px;font-size:16px;padding-top:30px;height:20px;min-height:40px;}.qq-upload-drop-area-active{background:#FF7171;}.qq-upload-list{margin:0;padding:0;list-style:none;}.qq-upload-list li{margin:0;padding:9px;line-height:15px;font-size:16px;background-color:#FFF0BD;}.qq-upload-file,.qq-upload-spinner,.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-failed-text,.qq-upload-finished,.qq-upload-delete{margin-right:12px;}.qq-upload-spinner{display:inline-block;background:url("loading.gif");width:15px;height:15px;vertical-align:text-bottom;}.qq-drop-processing{display:none;}.qq-drop-processing-spinner{display:inline-block;background:url("processing.gif");width:24px;height:24px;vertical-align:text-bottom;}.qq-upload-finished{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-retry,.qq-upload-delete{display:none;color:#000;}.qq-upload-cancel,.qq-upload-delete{color:#000;}.qq-upload-retryable .qq-upload-retry{display:inline;}.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-delete{font-size:12px;font-weight:normal;}.qq-upload-failed-text{display:none;font-style:italic;font-weight:bold;}.qq-upload-failed-icon{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-fail .qq-upload-failed-text{display:inline;}.qq-upload-retrying .qq-upload-failed-text{display:inline;color:#D60000;}.qq-upload-list li.qq-upload-success{background-color:#5DA30C;color:#FFF;}.qq-upload-list li.qq-upload-fail{background-color:#D60000;color:#FFF;}.qq-progress-bar{background:-moz-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,rgba(30,87,153,1)),color-stop(50%,rgba(41,137,216,1)),color-stop(51%,rgba(32,124,202,1)),color-stop(100%,rgba(125,185,232,1)));background:-webkit-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-o-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-ms-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:linear-gradient(to bottom,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);width:0;height:15px;border-radius:6px;margin-bottom:3px;display:none;}
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.4.0/fineuploader.min.js b/ajax/libs/file-uploader/3.4.0/fineuploader.min.js
new file mode 100644
index 000000000..2a50522e2
--- /dev/null
+++ b/ajax/libs/file-uploader/3.4.0/fineuploader.min.js
@@ -0,0 +1,13 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!==null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable!==null&&variable&&typeof variable==="object"&&variable.constructor===Object};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isString=function(maybeString){"use strict";return Object.prototype.toString.call(maybeString)==="[object String]"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFileOrInput=function(maybeFileOrInput){"use strict";if(qq.isBlob(maybeFileOrInput)&&window.File&&maybeFileOrInput instanceof File){return true}else if(window.HTMLInputElement){if(maybeFileOrInput instanceof HTMLInputElement){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}else if(maybeFileOrInput.tagName){if(maybeFileOrInput.tagName.toLowerCase()==="input"){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&maybeBlob instanceof Blob};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice||File.prototype.webkitSlice||File.prototype.mozSlice)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}})};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"misc_data",paramNames:{name:"qqblobname"}},paste:{targetElement:null,defaultName:"pasted_image"}};qq.extend(this._options,o,true);this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._netFilesUploadedOrQueued=0;this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}if(this._options.paste.targetElement){this._pasteHandler=this._createPasteHandler()}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._netFilesUploadedOrQueued++;this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset();this._pasteHandler.reset();this._netFilesUploadedOrQueued=0},addFiles:function(filesBlobDataOrInputs){var self=this,verifiedFilesOrInputs=[],index,fileOrInput;if(filesBlobDataOrInputs){if(!window.FileList||!(filesBlobDataOrInputs instanceof FileList)){filesBlobDataOrInputs=[].concat(filesBlobDataOrInputs)}for(index=0;index=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||this._options.cors.expected&&(qq.ie10()||!qq.ie()))},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)!==false){this._deleteHandler.sendDelete(id,this.getUuid(id))}}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(fileId){},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);if(isError){this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status,xhr)}else{this._netFilesUploadedOrQueued--;this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){},_onInputChange:function(input){if(qq.isXhrUploadSupported()){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0&&this._netFilesUploadedOrQueued+1>itemLimit){this._itemError("retryFailTooManyItems","");return false}this.log("Retrying upload for '"+fileName+"' (id: "+id+")...");this._filesInProgress.push(id);return true}else{this.log("'"+id+"' is not a valid file ID","error");return false}},_maybeParseAndSendUploadError:function(id,name,response,xhr){if(!response.success){if(xhr&&xhr.status!==200&&!response.error){this._options.callbacks.onError(id,name,"XHR returned response code "+xhr.status,xhr)}else{var errorReason=response.error?response.error:"Upload failure reason unknown";this._options.callbacks.onError(id,name,errorReason,xhr)}}},_uploadFileOrBlobDataList:function(fileOrBlobDataList){var index,validationDescriptors=this._getValidationDescriptors(fileOrBlobDataList),batchValid=this._isBatchValid(validationDescriptors);if(batchValid){if(fileOrBlobDataList.length>0){for(index=0;indexthis._options.validation.sizeLimit){this._itemError("sizeError",name);return false}else if(size&&size99);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,dirPending,droppedFiles=[],droppedEntriesCount=0,droppedEntriesParsedCount=0,disposeSupport=new qq.DisposeSupport;options={dropArea:null,extraDropzones:[],hideDropzones:true,multiple:true,classes:{dropActive:null},callbacks:{dropProcessing:function(isProcessing,files){},error:function(code,filename){},log:function(message,level){}}};
+qq.extend(options,o);function maybeUploadDroppedFiles(){if(droppedEntriesCount===droppedEntriesParsedCount&&!dirPending){options.callbacks.log("Grabbed "+droppedFiles.length+" files after tree traversal.");dz.dropDisabled(false);options.callbacks.dropProcessing(false,droppedFiles)}}function addDroppedFile(file){droppedFiles.push(file);droppedEntriesParsedCount+=1;maybeUploadDroppedFiles()}function traverseFileTree(entry){var dirReader,i;droppedEntriesCount+=1;if(entry.isFile){entry.file(function(file){addDroppedFile(file)})}else if(entry.isDirectory){dirPending=true;dirReader=entry.createReader();dirReader.readEntries(function(entries){droppedEntriesParsedCount+=1;for(i=0;i1&&!options.multiple){options.callbacks.dropProcessing(false);options.callbacks.error("tooManyFilesError","");dz.dropDisabled(false)}else{droppedFiles=[];droppedEntriesCount=0;droppedEntriesParsedCount=0;if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",dropDisabled:"qq-upload-drop-area-disabled",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file"},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false},paste:{promptForName:false,namePromptMessage:"Please name this image"},showMessage:function(message){setTimeout(function(){window.alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=window.confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)},showPrompt:function(message,defaultValue){var promise=new qq.Promise,retVal=window.prompt(message,defaultValue);if(retVal!=null&&qq.trimStr(retVal).length>0){promise.success(retVal)}else{promise.failure("Undefined or invalid user-supplied value.")}return promise}},true);qq.extend(this._options,o,true);this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop();if(this._options.paste.targetElement&&this._options.paste.promptForName){this._setupPastePrompt()}};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeExtraDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop()},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dnd,preventSelectFiles,defaultDropAreaEl;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){defaultDropAreaEl=this._find(this._options.element,"drop")}dnd=new qq.DragAndDrop({dropArea:defaultDropAreaEl,extraDropzones:this._options.dragAndDrop.extraDropzones,hideDropzones:this._options.dragAndDrop.hideDropzones,multiple:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{dropProcessing:function(isProcessing,files){var input=self._button.getInput();if(isProcessing){qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)}else{qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles)}if(files){self.addFiles(files)}},error:function(code,filename){self._itemError(code,filename)},log:function(message,level){self.log(message,level)}}});dnd.setup();return dnd},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){var item=this.getItemByFileId(id);if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}else{qq(item).addClass(this._classes.retryable);return false}},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)!==false){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}}}else{this.log("Delete request ignored for file ID "+id+", delete feature is disabled.","warn");return false}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}this._listElement.appendChild(item);if(this._options.display.fileSizeOnSubmit&&qq.isXhrUploadSupported()){this._displayFileSize(id)}},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=target=target.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_itemError:function(code,name){var message=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(message)},_batchError:function(message){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments);this._options.showMessage(message)},_setupPastePrompt:function(){var self=this;this._options.callbacks.onPasteReceived=function(){var message=self._options.paste.namePromptMessage,defaultVal=self._options.paste.defaultName;return self._options.showPrompt(message,defaultVal)}}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,dequeue,handlerImpl;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){}};qq.extend(options,o);log=options.log;dequeue=function(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){log("Cancelling "+id);options.paramsStore.remove(id);handlerImpl.cancel(id);dequeue(id)},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},getQueue:function(){return queue},reset:function(){log("Resetting upload handler");queue=[];handlerImpl.reset()},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}}};qq.UploadHandlerForm=function(o,uploadCompleteCallback,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var id=iframe.id;onloadCallbacks[uuids[id]]=callback;detachLoadEvents[id]=qq(iframe).attach("load",function(){if(inputs[id]){log("Received iframe load event for CORS upload request (file id "+id+")");postMessageCallbackTimers[id]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for file id "+id;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(id,function(message){log("Received the following window message: '"+message+"'");var response=qq.parseJson(message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];detachLoadEvent(id);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(id);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHTML=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHTML);if(innerHTML&&innerHTML.match(/^ ');iframe.setAttribute("id",id);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){if(api.isValid(id)){return inputs[id].value.replace(/.*(\/|\\)/,"")}else{log(id+" is not a valid item ID.","error")}},isValid:function(id){return inputs[id]!==undefined},reset:function(){inputs=[];uuids=[];detachLoadEvents={}},getUuid:function(id){return uuids[id]},cancel:function(id){options.onCancel(id,this.getName(id));delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(id);if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,this.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove();return id}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,logCallback){"use strict";
+var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.isFileChunkingSupported(),resumeEnabled=options.resume.enabled&&chunkFiles&&qq.areCookiesEnabled(),resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);delete fileState[id].xhr;uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(xhr){var response;try{response=qq.parseJson(xhr.responseText)}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function handleFileChunkingUpload(id,retry){var name=api.getName(id),firstChunkIndex=0,persistedChunkInfoForResume,firstChunkDataForResume,currentChunkIndex;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part);if(options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume))!==false){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].uuid=persistedChunkInfoForResume.uuid;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex)}}}for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}}uploadNextChunk(id)}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}api={add:function(fileOrBlobData){var id;if(fileOrBlobData instanceof File){id=fileState.push({file:fileOrBlobData})-1}else if(fileOrBlobData.blob instanceof Blob){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}fileState[id].uuid=qq.getUniqueId();return id},getName:function(id){if(api.isValid(id)){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}}else{log(id+" is not a valid item ID.","error")}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},getLoaded:function(id){return fileState[id].loaded||0},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}},cancel:function(id){var xhr=fileState[id].xhr;options.onCancel(id,this.getName(id));if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.4.0/iframe.xss.response.js b/ajax/libs/file-uploader/3.4.0/iframe.xss.response.js
new file mode 100644
index 000000000..e11fca1e5
--- /dev/null
+++ b/ajax/libs/file-uploader/3.4.0/iframe.xss.response.js
@@ -0,0 +1,6 @@
+(function() {
+ var match = /(\{.+\}).+/.exec(document.body.innerHTML);
+ if (match) {
+ parent.postMessage(match[1], '*');
+ }
+}());
diff --git a/ajax/libs/file-uploader/3.4.0/iframe.xss.response.min.js b/ajax/libs/file-uploader/3.4.0/iframe.xss.response.min.js
new file mode 100644
index 000000000..345cf3603
--- /dev/null
+++ b/ajax/libs/file-uploader/3.4.0/iframe.xss.response.min.js
@@ -0,0 +1 @@
+!function(){var match=/(\{.+\}).+/.exec(document.body.innerHTML);if(match){parent.postMessage(match[1],"*")}}();
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.4.0/loading.gif b/ajax/libs/file-uploader/3.4.0/loading.gif
new file mode 100644
index 000000000..6fba77609
Binary files /dev/null and b/ajax/libs/file-uploader/3.4.0/loading.gif differ
diff --git a/ajax/libs/file-uploader/3.4.0/processing.gif b/ajax/libs/file-uploader/3.4.0/processing.gif
new file mode 100644
index 000000000..7c99504e1
Binary files /dev/null and b/ajax/libs/file-uploader/3.4.0/processing.gif differ
diff --git a/ajax/libs/file-uploader/3.4.1/fineuploader-jquery.js b/ajax/libs/file-uploader/3.4.1/fineuploader-jquery.js
new file mode 100644
index 000000000..39b7a724d
--- /dev/null
+++ b/ajax/libs/file-uploader/3.4.1/fineuploader-jquery.js
@@ -0,0 +1,4217 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity !== null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+ if (qq.isBlob(maybeFileOrInput) && window.File && maybeFileOrInput instanceof File) {
+ return true;
+ }
+ else if (window.HTMLInputElement) {
+ if (maybeFileOrInput instanceof HTMLInputElement) {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ else if (maybeFileOrInput.tagName) {
+ if (maybeFileOrInput.tagName.toLowerCase() === 'input') {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && maybeBlob instanceof Blob;
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice || File.prototype.webkitSlice || File.prototype.mozSlice);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(obj, callback) {
+ "use strict";
+ var key, retVal;
+ if (obj) {
+ for (key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ retVal = callback(key, obj[key]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ c;
+
+ for(var i=0;i < ca.length;i++) {
+ c = ca[i];
+ while (c.charAt(0)==' ') {
+ c = c.substring(1,c.length);
+ }
+ if (c.indexOf(nameEQ) === 0) {
+ return c.substring(nameEQ.length,c.length);
+ }
+ }
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallback, failureCallback,
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ successCallback = onSuccess;
+ failureCallback = onFailure;
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallback) {
+ successCallback(val);
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallback) {
+ failureCallback(val);
+ }
+
+ return this;
+ }
+ };
+};/*globals qq*/
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ disposeSupport = new qq.DisposeSupport(),
+ options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input) {},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+
+ qq.extend(options, o);
+
+ // make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};qq.FineUploaderBasic = function(o){
+ var that = this;
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){},
+ onPasteReceived: function(blob) {
+ return new qq.Promise().success();
+ }
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'misc_data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ }
+ };
+
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._netFilesUploadedOrQueued = 0;
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function(){
+ return this._filesInProgress.length;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netFilesUploadedOrQueued++;
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._netFilesUploadedOrQueued = 0;
+
+ if (this._pasteHandler) {
+ this._pasteHandler.reset();
+ }
+ },
+ addFiles: function(filesBlobDataOrInputs) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ index, fileOrInput;
+
+ if (filesBlobDataOrInputs) {
+ if (!window.FileList || !(filesBlobDataOrInputs instanceof FileList)) {
+ filesBlobDataOrInputs = [].concat(filesBlobDataOrInputs);
+ }
+
+ for (index = 0; index < filesBlobDataOrInputs.length; index+=1) {
+ fileOrInput = filesBlobDataOrInputs[index];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
+ this._uploadFileOrBlobDataList(verifiedFilesOrInputs);
+ }
+ },
+ addBlobs: function(blobDataOrArray) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._uploadFileOrBlobDataList(verifiedBlobDataList);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getPromissoryCallbackNames: function() {
+ return ["onPasteReceived"];
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.isXhrUploadSupported(),
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result);
+ },
+ onCancel: function(id, name){
+ self._onCancel(id, name);
+ self._options.callbacks.onCancel(id, name);
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ var pasteReceivedCallback = self._options.callbacks.onPasteReceived,
+ promise = pasteReceivedCallback(blob);
+
+ if (promise.then) {
+ promise.then(function(successData) {
+ self._handlePasteSuccess(blob, successData);
+ }, function(failureData) {
+ self.log("Ignoring pasted image per paste received callback. Reason = '" + failureData + "'");
+ });
+ }
+ else {
+ self.log("Promise contract not fulfilled in pasteReceived callback handler! Ignoring pasted item.", "error");
+ }
+ }
+ }
+ });
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netFilesUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total){
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._netFilesUploadedOrQueued--;
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name){
+ this._netFilesUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected ||
+ (this._options.cors.expected && (qq.ie10() || !qq.ie()))
+ )
+ );
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id) !== false) {
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ }
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(fileId) {},
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
+ }
+ else {
+ this._netFilesUploadedOrQueued--;
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name){},
+ _onInputChange: function(input){
+ if (qq.isXhrUploadSupported()){
+ this.addFiles(input.files);
+ } else {
+ this.addFiles(input);
+ }
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netFilesUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems", "");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : "Upload failure reason unknown";
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _uploadFileOrBlobDataList: function(fileOrBlobDataList){
+ var index,
+ validationDescriptors = this._getValidationDescriptors(fileOrBlobDataList),
+ batchValid = this._isBatchValid(validationDescriptors);
+
+ if (batchValid) {
+ if (fileOrBlobDataList.length > 0) {
+ for (index = 0; index < fileOrBlobDataList.length; index++){
+ if (this._validateFileOrBlobData(fileOrBlobDataList[index])){
+ this._upload(fileOrBlobDataList[index]);
+ } else {
+ if (this._options.validation.stopOnFirstInvalidFile){
+ return;
+ }
+ }
+ }
+ }
+ else {
+ this._itemError("noFilesError", "");
+ }
+ }
+ },
+ _upload: function(blobOrFileContainer){
+ var id = this._handler.add(blobOrFileContainer);
+ var name = this._handler.getName(id);
+
+ if (this._options.callbacks.onSubmit(id, name) !== false) {
+ this._onSubmit(id, name);
+ this._options.callbacks.onSubmitted(id, name);
+
+ if (this._options.autoUpload) {
+ this._handler.upload(id);
+ }
+ else {
+ this._storeForLater(id);
+ }
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _isBatchValid: function(validationDescriptors) {
+ //first, defer the check to the callback (ask the integrator)
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netFilesUploadedOrQueued + validationDescriptors.length,
+ batchValid = this._options.callbacks.onValidateBatch(validationDescriptors) !== false;
+
+ //if the callback hasn't rejected the batch, run some internal tests on the batch next
+ if (batchValid) {
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ batchValid = true;
+ }
+ else {
+ batchValid = false;
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ }
+
+ return batchValid;
+ },
+ _validateFileOrBlobData: function(fileOrBlobData){
+ var validationDescriptor, name, size;
+
+ validationDescriptor = this._getValidationDescriptor(fileOrBlobData);
+ name = validationDescriptor.name;
+ size = validationDescriptor.size;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ return false;
+ }
+
+ if (qq.isFileOrInput(fileOrBlobData) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ return false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ return false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ return false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ return false;
+ }
+
+ return true;
+ },
+ _itemError: function(code, name) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ extensionsForMessage;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ this._options.callbacks.onError(null, name, message);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1024;
+ i++;
+ } while (bytes > 99);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz, dirPending,
+ droppedFiles = [],
+ droppedEntriesCount = 0,
+ droppedEntriesParsedCount = 0,
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropArea: null,
+ extraDropzones: [],
+ hideDropzones: true,
+ multiple: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {},
+ error: function(code, filename) {},
+ log: function(message, level) {}
+ }
+ };
+
+ qq.extend(options, o);
+
+ function maybeUploadDroppedFiles() {
+ if (droppedEntriesCount === droppedEntriesParsedCount && !dirPending) {
+ options.callbacks.log('Grabbed ' + droppedFiles.length + " files after tree traversal.");
+ dz.dropDisabled(false);
+ options.callbacks.dropProcessing(false, droppedFiles);
+ }
+ }
+ function addDroppedFile(file) {
+ droppedFiles.push(file);
+ droppedEntriesParsedCount+=1;
+ maybeUploadDroppedFiles();
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i;
+
+ droppedEntriesCount+=1;
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ addDroppedFile(file);
+ });
+ }
+ else if (entry.isDirectory) {
+ dirPending = true;
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ droppedEntriesParsedCount+=1;
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]);
+ }
+
+ dirPending = false;
+
+ if (!entries.length) {
+ maybeUploadDroppedFiles();
+ }
+ });
+ }
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry;
+
+ options.callbacks.dropProcessing(true);
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.multiple) {
+ options.callbacks.dropProcessing(false);
+ options.callbacks.error('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ }
+ else {
+ droppedFiles = [];
+ droppedEntriesCount = 0;
+ droppedEntriesParsedCount = 0;
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ if (i === items.length-1) {
+ maybeUploadDroppedFiles();
+ }
+ }
+
+ else {
+ traverseFileTree(entry);
+ }
+ }
+ }
+ }
+ else {
+ options.callbacks.dropProcessing(false, dataTransfer.files);
+ dz.dropDisabled(false);
+ }
+ }
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer);
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ if (options.dropArea) {
+ options.extraDropzones.push(options.dropArea);
+ }
+
+ var i, dropzones = options.extraDropzones;
+
+ for (i=0; i < dropzones.length; i+=1){
+ setupDropzone(dropzones[i]);
+ }
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (options.dropArea && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ if (qq(options.dropArea).hasClass(options.classes.dropDisabled)) {
+ return;
+ }
+
+ options.dropArea.style.display = 'block';
+ for (i=0; i < dropzones.length; i+=1) {
+ dropzones[i].style.display = 'block';
+ }
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropzones && qq.FineUploader.prototype._leaving_document_out(e)) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropzones) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setup: function() {
+ setupDragDrop();
+ },
+
+ setupExtraDropzone: function(element) {
+ options.extraDropzones.push(element);
+ setupDropzone(element);
+ },
+
+ removeExtraDropzone: function(element) {
+ var i, dzs = options.extraDropzones;
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ dropDisabled: 'qq-upload-drop-area-disabled',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file"
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeExtraDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dnd, preventSelectFiles, defaultDropAreaEl;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ defaultDropAreaEl = this._find(this._options.element, 'drop');
+ }
+
+ dnd = new qq.DragAndDrop({
+ dropArea: defaultDropAreaEl,
+ extraDropzones: this._options.dragAndDrop.extraDropzones,
+ hideDropzones: this._options.dragAndDrop.hideDropzones,
+ multiple: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {
+ var input = self._button.getInput();
+
+ if (isProcessing) {
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ }
+ else {
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+ }
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ error: function(code, filename) {
+ self._itemError(code, filename);
+ },
+ log: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+
+ dnd.setup();
+
+ return dnd;
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type){
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name){
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id) !== false) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ }
+ }
+ else {
+ this.log("Delete request ignored for file ID " + id + ", delete feature is disabled.", "warn");
+ return false;
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ this._listElement.appendChild(item);
+
+ if (this._options.display.fileSizeOnSubmit && qq.isXhrUploadSupported()) {
+ this._displayFileSize(id);
+ }
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = target = target.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, dequeue, handlerImpl;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ dequeue = function(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.isXhrUploadSupported()) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, log);
+ }
+
+
+ return {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ return handlerImpl.upload(id);
+ }
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ handlerImpl.cancel(id);
+ dequeue(id);
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ /**
+ * Returns id of files being uploaded or
+ * waiting for their turn
+ */
+ getQueue: function(){
+ return queue;
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ queue = [];
+ handlerImpl.reset();
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var id = iframe.id;
+
+ onloadCallbacks[uuids[id]] = callback;
+
+ detachLoadEvents[id] = qq(iframe).attach('load', function() {
+ if (inputs[id]) {
+ log("Received iframe load event for CORS upload request (file id " + id + ")");
+
+ postMessageCallbackTimers[id] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for file id " + id;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(id, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = qq.parseJson(message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+
+ detachLoadEvent(id);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(id);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHTML = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHTML);
+ //plain text response may be wrapped in tag
+ if (innerHTML && innerHTML.match(/^ ');
+
+ iframe.setAttribute('id', id);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ options.onCancel(id, this.getName(id));
+
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(id);
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ },
+ upload: function(id){
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, this.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+
+ return id;
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.isFileChunkingSupported(),
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.areCookiesEnabled(),
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+
+ options.onComplete(id, name, response, xhr);
+ delete fileState[id].xhr;
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var name = api.getName(id),
+ firstChunkIndex = 0,
+ persistedChunkInfoForResume, firstChunkDataForResume, currentChunkIndex;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part);
+ if (options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume)) !== false) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].uuid = persistedChunkInfoForResume.uuid;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+ }
+ }
+ }
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id;
+
+ if (fileOrBlobData instanceof File) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (fileOrBlobData.blob instanceof Blob) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ fileState[id].uuid = qq.getUniqueId();
+ return id;
+ },
+ getName: function(id){
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ /**
+ * Returns uploaded bytes for file identified by id
+ */
+ getLoaded: function(id){
+ return fileState[id].loaded || 0;
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry){
+ var name = this.getName(id);
+
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ },
+ cancel: function(id){
+ var xhr = fileState[id].xhr;
+
+ options.onCancel(id, this.getName(id));
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var uploader, $el, init, dataStore, pluginOption, pluginOptions, addCallbacks, transformVariables, isValidCommand,
+ delegateCommand;
+
+ pluginOptions = ['uploaderType'];
+
+ init = function (options) {
+ if (options) {
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+
+ if (pluginOption('uploaderType') === 'basic') {
+ uploader(new qq.FineUploaderBasic(xformedOpts));
+ }
+ else {
+ uploader(new qq.FineUploader(xformedOpts));
+ }
+ }
+
+ return $el;
+ };
+
+ dataStore = function(key, val) {
+ var data = $el.data('fineuploader');
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data('fineuploader', data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ //the underlying Fine Uploader instance is stored in jQuery's data stored, associated with the element
+ // tied to this instance of the plug-in
+ uploader = function(instanceToStore) {
+ return dataStore('uploader', instanceToStore);
+ };
+
+ pluginOption = function(option, optionVal) {
+ return dataStore(option, optionVal);
+ };
+
+ //implement all callbacks defined in Fine Uploader as functions that trigger appropriately names events and
+ // return the result of executing the bound handler back to Fine Uploader
+ addCallbacks = function(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {},
+ uploaderInst = new qq.FineUploaderBasic();
+
+ $.each(uploaderInst._options.callbacks, function(prop, func) {
+ var name, $callbackEl;
+
+ name = /^on(\w+)/.exec(prop)[1];
+ name = name.substring(0, 1).toLowerCase() + name.substring(1);
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var origFunc = func,
+ args = Array.prototype.slice.call(arguments),
+ jqueryHandlerResult = $callbackEl.triggerHandler(name, args);
+
+ if (jqueryHandlerResult === undefined &&
+ $.inArray(prop, uploaderInst.getPromissoryCallbackNames()) >= 0) {
+ return origFunc();
+ }
+
+ return jqueryHandlerResult;
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ transformVariables = function(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ if (source.uploaderType !== 'basic') {
+ xformed = { element : $el[0] };
+ }
+ else {
+ xformed = {};
+ }
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if ($.inArray(prop, pluginOptions) >= 0) {
+ pluginOption(prop, val);
+ }
+ else if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ isValidCommand = function(command) {
+ return $.type(command) === "string" &&
+ !command.match(/^_/) && //enforce private methods convention
+ uploader()[command] !== undefined;
+ };
+
+ //assuming we have already verified that this is a valid command, call the associated function in the underlying
+ // Fine Uploader instance (passing along the arguments from the caller) and return the result of the call back to the caller
+ delegateCommand = function(command) {
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
+
+ transformVariables(origArgs, xformedArgs);
+
+ return uploader()[command].apply(uploader(), xformedArgs);
+ };
+
+ $.fn.fineUploader = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (uploader() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error('Method ' + optionsOrCommand + ' does not exist on jQuery.fineUploader');
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
diff --git a/ajax/libs/file-uploader/3.4.1/fineuploader-jquery.min.js b/ajax/libs/file-uploader/3.4.1/fineuploader-jquery.min.js
new file mode 100644
index 000000000..3ee783d7f
--- /dev/null
+++ b/ajax/libs/file-uploader/3.4.1/fineuploader-jquery.min.js
@@ -0,0 +1,13 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!==null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable!==null&&variable&&typeof variable==="object"&&variable.constructor===Object};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isString=function(maybeString){"use strict";return Object.prototype.toString.call(maybeString)==="[object String]"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFileOrInput=function(maybeFileOrInput){"use strict";if(qq.isBlob(maybeFileOrInput)&&window.File&&maybeFileOrInput instanceof File){return true}else if(window.HTMLInputElement){if(maybeFileOrInput instanceof HTMLInputElement){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}else if(maybeFileOrInput.tagName){if(maybeFileOrInput.tagName.toLowerCase()==="input"){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&maybeBlob instanceof Blob};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice||File.prototype.webkitSlice||File.prototype.mozSlice)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}})};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"misc_data",paramNames:{name:"qqblobname"}},paste:{targetElement:null,defaultName:"pasted_image"}};qq.extend(this._options,o,true);this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._netFilesUploadedOrQueued=0;this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}if(this._options.paste.targetElement){this._pasteHandler=this._createPasteHandler()}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._netFilesUploadedOrQueued++;this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset();this._netFilesUploadedOrQueued=0;if(this._pasteHandler){this._pasteHandler.reset()}},addFiles:function(filesBlobDataOrInputs){var self=this,verifiedFilesOrInputs=[],index,fileOrInput;if(filesBlobDataOrInputs){if(!window.FileList||!(filesBlobDataOrInputs instanceof FileList)){filesBlobDataOrInputs=[].concat(filesBlobDataOrInputs)}for(index=0;index=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||this._options.cors.expected&&(qq.ie10()||!qq.ie()))},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)!==false){this._deleteHandler.sendDelete(id,this.getUuid(id))}}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(fileId){},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);if(isError){this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status,xhr)}else{this._netFilesUploadedOrQueued--;this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){},_onInputChange:function(input){if(qq.isXhrUploadSupported()){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0&&this._netFilesUploadedOrQueued+1>itemLimit){this._itemError("retryFailTooManyItems","");return false}this.log("Retrying upload for '"+fileName+"' (id: "+id+")...");this._filesInProgress.push(id);return true}else{this.log("'"+id+"' is not a valid file ID","error");return false}},_maybeParseAndSendUploadError:function(id,name,response,xhr){if(!response.success){if(xhr&&xhr.status!==200&&!response.error){this._options.callbacks.onError(id,name,"XHR returned response code "+xhr.status,xhr)}else{var errorReason=response.error?response.error:"Upload failure reason unknown";this._options.callbacks.onError(id,name,errorReason,xhr)}}},_uploadFileOrBlobDataList:function(fileOrBlobDataList){var index,validationDescriptors=this._getValidationDescriptors(fileOrBlobDataList),batchValid=this._isBatchValid(validationDescriptors);if(batchValid){if(fileOrBlobDataList.length>0){for(index=0;indexthis._options.validation.sizeLimit){this._itemError("sizeError",name);return false}else if(size&&size99);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,dirPending,droppedFiles=[],droppedEntriesCount=0,droppedEntriesParsedCount=0,disposeSupport=new qq.DisposeSupport;options={dropArea:null,extraDropzones:[],hideDropzones:true,multiple:true,classes:{dropActive:null},callbacks:{dropProcessing:function(isProcessing,files){},error:function(code,filename){},log:function(message,level){}}};
+qq.extend(options,o);function maybeUploadDroppedFiles(){if(droppedEntriesCount===droppedEntriesParsedCount&&!dirPending){options.callbacks.log("Grabbed "+droppedFiles.length+" files after tree traversal.");dz.dropDisabled(false);options.callbacks.dropProcessing(false,droppedFiles)}}function addDroppedFile(file){droppedFiles.push(file);droppedEntriesParsedCount+=1;maybeUploadDroppedFiles()}function traverseFileTree(entry){var dirReader,i;droppedEntriesCount+=1;if(entry.isFile){entry.file(function(file){addDroppedFile(file)})}else if(entry.isDirectory){dirPending=true;dirReader=entry.createReader();dirReader.readEntries(function(entries){droppedEntriesParsedCount+=1;for(i=0;i1&&!options.multiple){options.callbacks.dropProcessing(false);options.callbacks.error("tooManyFilesError","");dz.dropDisabled(false)}else{droppedFiles=[];droppedEntriesCount=0;droppedEntriesParsedCount=0;if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",dropDisabled:"qq-upload-drop-area-disabled",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file"},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false},paste:{promptForName:false,namePromptMessage:"Please name this image"},showMessage:function(message){setTimeout(function(){window.alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=window.confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)},showPrompt:function(message,defaultValue){var promise=new qq.Promise,retVal=window.prompt(message,defaultValue);if(retVal!=null&&qq.trimStr(retVal).length>0){promise.success(retVal)}else{promise.failure("Undefined or invalid user-supplied value.")}return promise}},true);qq.extend(this._options,o,true);this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop();if(this._options.paste.targetElement&&this._options.paste.promptForName){this._setupPastePrompt()}};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeExtraDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop()},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dnd,preventSelectFiles,defaultDropAreaEl;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){defaultDropAreaEl=this._find(this._options.element,"drop")}dnd=new qq.DragAndDrop({dropArea:defaultDropAreaEl,extraDropzones:this._options.dragAndDrop.extraDropzones,hideDropzones:this._options.dragAndDrop.hideDropzones,multiple:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{dropProcessing:function(isProcessing,files){var input=self._button.getInput();if(isProcessing){qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)}else{qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles)}if(files){self.addFiles(files)}},error:function(code,filename){self._itemError(code,filename)},log:function(message,level){self.log(message,level)}}});dnd.setup();return dnd},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){var item=this.getItemByFileId(id);if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}else{qq(item).addClass(this._classes.retryable);return false}},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)!==false){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}}}else{this.log("Delete request ignored for file ID "+id+", delete feature is disabled.","warn");return false}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}this._listElement.appendChild(item);if(this._options.display.fileSizeOnSubmit&&qq.isXhrUploadSupported()){this._displayFileSize(id)}},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=target=target.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_itemError:function(code,name){var message=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(message)},_batchError:function(message){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments);this._options.showMessage(message)},_setupPastePrompt:function(){var self=this;this._options.callbacks.onPasteReceived=function(){var message=self._options.paste.namePromptMessage,defaultVal=self._options.paste.defaultName;return self._options.showPrompt(message,defaultVal)}}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,dequeue,handlerImpl;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){}};qq.extend(options,o);log=options.log;dequeue=function(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){log("Cancelling "+id);options.paramsStore.remove(id);handlerImpl.cancel(id);dequeue(id)},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},getQueue:function(){return queue},reset:function(){log("Resetting upload handler");queue=[];handlerImpl.reset()},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}}};qq.UploadHandlerForm=function(o,uploadCompleteCallback,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var id=iframe.id;onloadCallbacks[uuids[id]]=callback;detachLoadEvents[id]=qq(iframe).attach("load",function(){if(inputs[id]){log("Received iframe load event for CORS upload request (file id "+id+")");postMessageCallbackTimers[id]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for file id "+id;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(id,function(message){log("Received the following window message: '"+message+"'");var response=qq.parseJson(message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];detachLoadEvent(id);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(id);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHTML=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHTML);if(innerHTML&&innerHTML.match(/^ ');iframe.setAttribute("id",id);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){if(api.isValid(id)){return inputs[id].value.replace(/.*(\/|\\)/,"")}else{log(id+" is not a valid item ID.","error")}},isValid:function(id){return inputs[id]!==undefined},reset:function(){inputs=[];uuids=[];detachLoadEvents={}},getUuid:function(id){return uuids[id]},cancel:function(id){options.onCancel(id,this.getName(id));delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(id);if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,this.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove();return id}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,logCallback){"use strict";
+var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.isFileChunkingSupported(),resumeEnabled=options.resume.enabled&&chunkFiles&&qq.areCookiesEnabled(),resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);delete fileState[id].xhr;uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(xhr){var response;try{response=qq.parseJson(xhr.responseText)}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function handleFileChunkingUpload(id,retry){var name=api.getName(id),firstChunkIndex=0,persistedChunkInfoForResume,firstChunkDataForResume,currentChunkIndex;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part);if(options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume))!==false){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].uuid=persistedChunkInfoForResume.uuid;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex)}}}for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}}uploadNextChunk(id)}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}api={add:function(fileOrBlobData){var id;if(fileOrBlobData instanceof File){id=fileState.push({file:fileOrBlobData})-1}else if(fileOrBlobData.blob instanceof Blob){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}fileState[id].uuid=qq.getUniqueId();return id},getName:function(id){if(api.isValid(id)){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}}else{log(id+" is not a valid item ID.","error")}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},getLoaded:function(id){return fileState[id].loaded||0},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}},cancel:function(id){var xhr=fileState[id].xhr;options.onCancel(id,this.getName(id));if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};!function($){"use strict";var uploader,$el,init,dataStore,pluginOption,pluginOptions,addCallbacks,transformVariables,isValidCommand,delegateCommand;pluginOptions=["uploaderType"];init=function(options){if(options){var xformedOpts=transformVariables(options);addCallbacks(xformedOpts);if(pluginOption("uploaderType")==="basic"){uploader(new qq.FineUploaderBasic(xformedOpts))}else{uploader(new qq.FineUploader(xformedOpts))}}return $el};dataStore=function(key,val){var data=$el.data("fineuploader");if(val){if(data===undefined){data={}}data[key]=val;$el.data("fineuploader",data)}else{if(data===undefined){return null}return data[key]}};uploader=function(instanceToStore){return dataStore("uploader",instanceToStore)};pluginOption=function(option,optionVal){return dataStore(option,optionVal)};addCallbacks=function(transformedOpts){var callbacks=transformedOpts.callbacks={},uploaderInst=new qq.FineUploaderBasic;$.each(uploaderInst._options.callbacks,function(prop,func){var name,$callbackEl;name=/^on(\w+)/.exec(prop)[1];name=name.substring(0,1).toLowerCase()+name.substring(1);$callbackEl=$el;callbacks[prop]=function(){var origFunc=func,args=Array.prototype.slice.call(arguments),jqueryHandlerResult=$callbackEl.triggerHandler(name,args);if(jqueryHandlerResult===undefined&&$.inArray(prop,uploaderInst.getPromissoryCallbackNames())>=0){return origFunc()}return jqueryHandlerResult}})};transformVariables=function(source,dest){var xformed,arrayVals;if(dest===undefined){if(source.uploaderType!=="basic"){xformed={element:$el[0]}}else{xformed={}}}else{xformed=dest}$.each(source,function(prop,val){if($.inArray(prop,pluginOptions)>=0){pluginOption(prop,val)}else if(val instanceof $){xformed[prop]=val[0]}else if($.isPlainObject(val)){xformed[prop]={};transformVariables(val,xformed[prop])}else if($.isArray(val)){arrayVals=[];$.each(val,function(idx,arrayVal){if(arrayVal instanceof $){$.merge(arrayVals,arrayVal)}else{arrayVals.push(arrayVal)}});xformed[prop]=arrayVals}else{xformed[prop]=val}});if(dest===undefined){return xformed}};isValidCommand=function(command){return $.type(command)==="string"&&!command.match(/^_/)&&uploader()[command]!==undefined};delegateCommand=function(command){var xformedArgs=[],origArgs=Array.prototype.slice.call(arguments,1);transformVariables(origArgs,xformedArgs);return uploader()[command].apply(uploader(),xformedArgs)};$.fn.fineUploader=function(optionsOrCommand){var self=this,selfArgs=arguments,retVals=[];this.each(function(index,el){$el=$(el);if(uploader()&&isValidCommand(optionsOrCommand)){retVals.push(delegateCommand.apply(self,selfArgs));if(self.length===1){return false}}else if(typeof optionsOrCommand==="object"||!optionsOrCommand){init.apply(self,selfArgs)}else{$.error("Method "+optionsOrCommand+" does not exist on jQuery.fineUploader")}});if(retVals.length===1){return retVals[0]}else if(retVals.length>1){return retVals}return this}}(jQuery);
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.4.1/fineuploader.css b/ajax/libs/file-uploader/3.4.1/fineuploader.css
new file mode 100644
index 000000000..11a436a06
--- /dev/null
+++ b/ajax/libs/file-uploader/3.4.1/fineuploader.css
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2013, Widen Enterprises info@fineuploader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+.qq-uploader {
+ position: relative;
+ width: 100%;
+}
+.qq-upload-button {
+ display: block;
+ width: 105px;
+ padding: 7px 0;
+ text-align: center;
+ background: #880000;
+ border-bottom: 1px solid #DDD;
+ color: #FFF;
+}
+.qq-upload-button-hover {
+ background: #CC0000;
+}
+.qq-upload-button-focus {
+ outline: 1px dotted #000000;
+}
+.qq-upload-drop-area, .qq-upload-extra-drop-area {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ min-height: 30px;
+ z-index: 2;
+ background: #FF9797;
+ text-align: center;
+}
+.qq-upload-drop-area span {
+ display: block;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ margin-top: -8px;
+ font-size: 16px;
+}
+.qq-upload-extra-drop-area {
+ position: relative;
+ margin-top: 50px;
+ font-size: 16px;
+ padding-top: 30px;
+ height: 20px;
+ min-height: 40px;
+}
+.qq-upload-drop-area-active {
+ background: #FF7171;
+}
+.qq-upload-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+.qq-upload-list li {
+ margin: 0;
+ padding: 9px;
+ line-height: 15px;
+ font-size: 16px;
+ background-color: #FFF0BD;
+}
+.qq-upload-file, .qq-upload-spinner, .qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text, .qq-upload-finished, .qq-upload-delete {
+ margin-right: 12px;
+}
+.qq-upload-file {
+}
+.qq-upload-spinner {
+ display: inline-block;
+ background: url("loading.gif");
+ width: 15px;
+ height: 15px;
+ vertical-align: text-bottom;
+}
+.qq-drop-processing {
+ display: none;
+}
+.qq-drop-processing-spinner {
+ display: inline-block;
+ background: url("processing.gif");
+ width: 24px;
+ height: 24px;
+ vertical-align: text-bottom;
+}
+.qq-upload-finished {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-retry, .qq-upload-delete {
+ display: none;
+ color: #000000;
+}
+.qq-upload-cancel, .qq-upload-delete {
+ color: #000000;
+}
+.qq-upload-retryable .qq-upload-retry {
+ display: inline;
+}
+.qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-delete {
+ font-size: 12px;
+ font-weight: normal;
+}
+.qq-upload-failed-text {
+ display: none;
+ font-style: italic;
+ font-weight: bold;
+}
+.qq-upload-failed-icon {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-fail .qq-upload-failed-text {
+ display: inline;
+}
+.qq-upload-retrying .qq-upload-failed-text {
+ display: inline;
+ color: #D60000;
+}
+.qq-upload-list li.qq-upload-success {
+ background-color: #5DA30C;
+ color: #FFFFFF;
+}
+.qq-upload-list li.qq-upload-fail {
+ background-color: #D60000;
+ color: #FFFFFF;
+}
+.qq-progress-bar {
+ background: -moz-linear-gradient(top, rgba(30,87,153,1) 0%, rgba(41,137,216,1) 50%, rgba(32,124,202,1) 51%, rgba(125,185,232,1) 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(30,87,153,1)), color-stop(50%,rgba(41,137,216,1)), color-stop(51%,rgba(32,124,202,1)), color-stop(100%,rgba(125,185,232,1))); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* IE10+ */
+ background: linear-gradient(to bottom, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* W3C */
+ width: 0%;
+ height: 15px;
+ border-radius: 6px;
+ margin-bottom: 3px;
+ display: none;
+}
diff --git a/ajax/libs/file-uploader/3.4.1/fineuploader.js b/ajax/libs/file-uploader/3.4.1/fineuploader.js
new file mode 100644
index 000000000..ea0847d55
--- /dev/null
+++ b/ajax/libs/file-uploader/3.4.1/fineuploader.js
@@ -0,0 +1,4040 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity !== null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+ if (qq.isBlob(maybeFileOrInput) && window.File && maybeFileOrInput instanceof File) {
+ return true;
+ }
+ else if (window.HTMLInputElement) {
+ if (maybeFileOrInput instanceof HTMLInputElement) {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ else if (maybeFileOrInput.tagName) {
+ if (maybeFileOrInput.tagName.toLowerCase() === 'input') {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && maybeBlob instanceof Blob;
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice || File.prototype.webkitSlice || File.prototype.mozSlice);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(obj, callback) {
+ "use strict";
+ var key, retVal;
+ if (obj) {
+ for (key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ retVal = callback(key, obj[key]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ c;
+
+ for(var i=0;i < ca.length;i++) {
+ c = ca[i];
+ while (c.charAt(0)==' ') {
+ c = c.substring(1,c.length);
+ }
+ if (c.indexOf(nameEQ) === 0) {
+ return c.substring(nameEQ.length,c.length);
+ }
+ }
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallback, failureCallback,
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ successCallback = onSuccess;
+ failureCallback = onFailure;
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallback) {
+ successCallback(val);
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallback) {
+ failureCallback(val);
+ }
+
+ return this;
+ }
+ };
+};/*globals qq*/
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ disposeSupport = new qq.DisposeSupport(),
+ options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input) {},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+
+ qq.extend(options, o);
+
+ // make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};qq.FineUploaderBasic = function(o){
+ var that = this;
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){},
+ onPasteReceived: function(blob) {
+ return new qq.Promise().success();
+ }
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'misc_data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ }
+ };
+
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._netFilesUploadedOrQueued = 0;
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function(){
+ return this._filesInProgress.length;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netFilesUploadedOrQueued++;
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._netFilesUploadedOrQueued = 0;
+
+ if (this._pasteHandler) {
+ this._pasteHandler.reset();
+ }
+ },
+ addFiles: function(filesBlobDataOrInputs) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ index, fileOrInput;
+
+ if (filesBlobDataOrInputs) {
+ if (!window.FileList || !(filesBlobDataOrInputs instanceof FileList)) {
+ filesBlobDataOrInputs = [].concat(filesBlobDataOrInputs);
+ }
+
+ for (index = 0; index < filesBlobDataOrInputs.length; index+=1) {
+ fileOrInput = filesBlobDataOrInputs[index];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
+ this._uploadFileOrBlobDataList(verifiedFilesOrInputs);
+ }
+ },
+ addBlobs: function(blobDataOrArray) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._uploadFileOrBlobDataList(verifiedBlobDataList);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getPromissoryCallbackNames: function() {
+ return ["onPasteReceived"];
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.isXhrUploadSupported(),
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result);
+ },
+ onCancel: function(id, name){
+ self._onCancel(id, name);
+ self._options.callbacks.onCancel(id, name);
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ var pasteReceivedCallback = self._options.callbacks.onPasteReceived,
+ promise = pasteReceivedCallback(blob);
+
+ if (promise.then) {
+ promise.then(function(successData) {
+ self._handlePasteSuccess(blob, successData);
+ }, function(failureData) {
+ self.log("Ignoring pasted image per paste received callback. Reason = '" + failureData + "'");
+ });
+ }
+ else {
+ self.log("Promise contract not fulfilled in pasteReceived callback handler! Ignoring pasted item.", "error");
+ }
+ }
+ }
+ });
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netFilesUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total){
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._netFilesUploadedOrQueued--;
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name){
+ this._netFilesUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected ||
+ (this._options.cors.expected && (qq.ie10() || !qq.ie()))
+ )
+ );
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id) !== false) {
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ }
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(fileId) {},
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
+ }
+ else {
+ this._netFilesUploadedOrQueued--;
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name){},
+ _onInputChange: function(input){
+ if (qq.isXhrUploadSupported()){
+ this.addFiles(input.files);
+ } else {
+ this.addFiles(input);
+ }
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netFilesUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems", "");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : "Upload failure reason unknown";
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _uploadFileOrBlobDataList: function(fileOrBlobDataList){
+ var index,
+ validationDescriptors = this._getValidationDescriptors(fileOrBlobDataList),
+ batchValid = this._isBatchValid(validationDescriptors);
+
+ if (batchValid) {
+ if (fileOrBlobDataList.length > 0) {
+ for (index = 0; index < fileOrBlobDataList.length; index++){
+ if (this._validateFileOrBlobData(fileOrBlobDataList[index])){
+ this._upload(fileOrBlobDataList[index]);
+ } else {
+ if (this._options.validation.stopOnFirstInvalidFile){
+ return;
+ }
+ }
+ }
+ }
+ else {
+ this._itemError("noFilesError", "");
+ }
+ }
+ },
+ _upload: function(blobOrFileContainer){
+ var id = this._handler.add(blobOrFileContainer);
+ var name = this._handler.getName(id);
+
+ if (this._options.callbacks.onSubmit(id, name) !== false) {
+ this._onSubmit(id, name);
+ this._options.callbacks.onSubmitted(id, name);
+
+ if (this._options.autoUpload) {
+ this._handler.upload(id);
+ }
+ else {
+ this._storeForLater(id);
+ }
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _isBatchValid: function(validationDescriptors) {
+ //first, defer the check to the callback (ask the integrator)
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netFilesUploadedOrQueued + validationDescriptors.length,
+ batchValid = this._options.callbacks.onValidateBatch(validationDescriptors) !== false;
+
+ //if the callback hasn't rejected the batch, run some internal tests on the batch next
+ if (batchValid) {
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ batchValid = true;
+ }
+ else {
+ batchValid = false;
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ }
+
+ return batchValid;
+ },
+ _validateFileOrBlobData: function(fileOrBlobData){
+ var validationDescriptor, name, size;
+
+ validationDescriptor = this._getValidationDescriptor(fileOrBlobData);
+ name = validationDescriptor.name;
+ size = validationDescriptor.size;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ return false;
+ }
+
+ if (qq.isFileOrInput(fileOrBlobData) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ return false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ return false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ return false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ return false;
+ }
+
+ return true;
+ },
+ _itemError: function(code, name) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ extensionsForMessage;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ this._options.callbacks.onError(null, name, message);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1024;
+ i++;
+ } while (bytes > 99);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz, dirPending,
+ droppedFiles = [],
+ droppedEntriesCount = 0,
+ droppedEntriesParsedCount = 0,
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropArea: null,
+ extraDropzones: [],
+ hideDropzones: true,
+ multiple: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {},
+ error: function(code, filename) {},
+ log: function(message, level) {}
+ }
+ };
+
+ qq.extend(options, o);
+
+ function maybeUploadDroppedFiles() {
+ if (droppedEntriesCount === droppedEntriesParsedCount && !dirPending) {
+ options.callbacks.log('Grabbed ' + droppedFiles.length + " files after tree traversal.");
+ dz.dropDisabled(false);
+ options.callbacks.dropProcessing(false, droppedFiles);
+ }
+ }
+ function addDroppedFile(file) {
+ droppedFiles.push(file);
+ droppedEntriesParsedCount+=1;
+ maybeUploadDroppedFiles();
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i;
+
+ droppedEntriesCount+=1;
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ addDroppedFile(file);
+ });
+ }
+ else if (entry.isDirectory) {
+ dirPending = true;
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ droppedEntriesParsedCount+=1;
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]);
+ }
+
+ dirPending = false;
+
+ if (!entries.length) {
+ maybeUploadDroppedFiles();
+ }
+ });
+ }
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry;
+
+ options.callbacks.dropProcessing(true);
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.multiple) {
+ options.callbacks.dropProcessing(false);
+ options.callbacks.error('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ }
+ else {
+ droppedFiles = [];
+ droppedEntriesCount = 0;
+ droppedEntriesParsedCount = 0;
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ if (i === items.length-1) {
+ maybeUploadDroppedFiles();
+ }
+ }
+
+ else {
+ traverseFileTree(entry);
+ }
+ }
+ }
+ }
+ else {
+ options.callbacks.dropProcessing(false, dataTransfer.files);
+ dz.dropDisabled(false);
+ }
+ }
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer);
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropzones) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ if (options.dropArea) {
+ options.extraDropzones.push(options.dropArea);
+ }
+
+ var i, dropzones = options.extraDropzones;
+
+ for (i=0; i < dropzones.length; i+=1){
+ setupDropzone(dropzones[i]);
+ }
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (options.dropArea && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ if (qq(options.dropArea).hasClass(options.classes.dropDisabled)) {
+ return;
+ }
+
+ options.dropArea.style.display = 'block';
+ for (i=0; i < dropzones.length; i+=1) {
+ dropzones[i].style.display = 'block';
+ }
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropzones && qq.FineUploader.prototype._leaving_document_out(e)) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropzones) {
+ for (i=0; i < dropzones.length; i+=1) {
+ qq(dropzones[i]).hide();
+ }
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setup: function() {
+ setupDragDrop();
+ },
+
+ setupExtraDropzone: function(element) {
+ options.extraDropzones.push(element);
+ setupDropzone(element);
+ },
+
+ removeExtraDropzone: function(element) {
+ var i, dzs = options.extraDropzones;
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ dropDisabled: 'qq-upload-drop-area-disabled',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file"
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeExtraDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dnd, preventSelectFiles, defaultDropAreaEl;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ defaultDropAreaEl = this._find(this._options.element, 'drop');
+ }
+
+ dnd = new qq.DragAndDrop({
+ dropArea: defaultDropAreaEl,
+ extraDropzones: this._options.dragAndDrop.extraDropzones,
+ hideDropzones: this._options.dragAndDrop.hideDropzones,
+ multiple: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ dropProcessing: function(isProcessing, files) {
+ var input = self._button.getInput();
+
+ if (isProcessing) {
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ }
+ else {
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+ }
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ error: function(code, filename) {
+ self._itemError(code, filename);
+ },
+ log: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+
+ dnd.setup();
+
+ return dnd;
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type){
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name){
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id) !== false) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ }
+ }
+ else {
+ this.log("Delete request ignored for file ID " + id + ", delete feature is disabled.", "warn");
+ return false;
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ this._listElement.appendChild(item);
+
+ if (this._options.display.fileSizeOnSubmit && qq.isXhrUploadSupported()) {
+ this._displayFileSize(id);
+ }
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = target = target.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.isXhrUploadSupported()) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, dequeue, handlerImpl;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ dequeue = function(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.isXhrUploadSupported()) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, log);
+ }
+
+
+ return {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ return handlerImpl.upload(id);
+ }
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ handlerImpl.cancel(id);
+ dequeue(id);
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ /**
+ * Returns id of files being uploaded or
+ * waiting for their turn
+ */
+ getQueue: function(){
+ return queue;
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ queue = [];
+ handlerImpl.reset();
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var id = iframe.id;
+
+ onloadCallbacks[uuids[id]] = callback;
+
+ detachLoadEvents[id] = qq(iframe).attach('load', function() {
+ if (inputs[id]) {
+ log("Received iframe load event for CORS upload request (file id " + id + ")");
+
+ postMessageCallbackTimers[id] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for file id " + id;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(id, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = qq.parseJson(message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+
+ detachLoadEvent(id);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(id);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHTML = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHTML);
+ //plain text response may be wrapped in tag
+ if (innerHTML && innerHTML.match(/^ ');
+
+ iframe.setAttribute('id', id);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ options.onCancel(id, this.getName(id));
+
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(id);
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ },
+ upload: function(id){
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, this.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+
+ return id;
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.isFileChunkingSupported(),
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.areCookiesEnabled(),
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+
+ options.onComplete(id, name, response, xhr);
+ delete fileState[id].xhr;
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var name = api.getName(id),
+ firstChunkIndex = 0,
+ persistedChunkInfoForResume, firstChunkDataForResume, currentChunkIndex;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part);
+ if (options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume)) !== false) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].uuid = persistedChunkInfoForResume.uuid;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+ }
+ }
+ }
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id;
+
+ if (fileOrBlobData instanceof File) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (fileOrBlobData.blob instanceof Blob) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ fileState[id].uuid = qq.getUniqueId();
+ return id;
+ },
+ getName: function(id){
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ /**
+ * Returns uploaded bytes for file identified by id
+ */
+ getLoaded: function(id){
+ return fileState[id].loaded || 0;
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry){
+ var name = this.getName(id);
+
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ },
+ cancel: function(id){
+ var xhr = fileState[id].xhr;
+
+ options.onCancel(id, this.getName(id));
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
diff --git a/ajax/libs/file-uploader/3.4.1/fineuploader.min.css b/ajax/libs/file-uploader/3.4.1/fineuploader.min.css
new file mode 100644
index 000000000..110e8fa52
--- /dev/null
+++ b/ajax/libs/file-uploader/3.4.1/fineuploader.min.css
@@ -0,0 +1 @@
+.qq-uploader{position:relative;width:100%;}.qq-upload-button{display:block;width:105px;padding:7px 0;text-align:center;background:#800;border-bottom:1px solid #DDD;color:#FFF;}.qq-upload-button-hover{background:#C00;}.qq-upload-button-focus{outline:1px dotted #000;}.qq-upload-drop-area,.qq-upload-extra-drop-area{position:absolute;top:0;left:0;width:100%;height:100%;min-height:30px;z-index:2;background:#FF9797;text-align:center;}.qq-upload-drop-area span{display:block;position:absolute;top:50%;width:100%;margin-top:-8px;font-size:16px;}.qq-upload-extra-drop-area{position:relative;margin-top:50px;font-size:16px;padding-top:30px;height:20px;min-height:40px;}.qq-upload-drop-area-active{background:#FF7171;}.qq-upload-list{margin:0;padding:0;list-style:none;}.qq-upload-list li{margin:0;padding:9px;line-height:15px;font-size:16px;background-color:#FFF0BD;}.qq-upload-file,.qq-upload-spinner,.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-failed-text,.qq-upload-finished,.qq-upload-delete{margin-right:12px;}.qq-upload-spinner{display:inline-block;background:url("loading.gif");width:15px;height:15px;vertical-align:text-bottom;}.qq-drop-processing{display:none;}.qq-drop-processing-spinner{display:inline-block;background:url("processing.gif");width:24px;height:24px;vertical-align:text-bottom;}.qq-upload-finished{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-retry,.qq-upload-delete{display:none;color:#000;}.qq-upload-cancel,.qq-upload-delete{color:#000;}.qq-upload-retryable .qq-upload-retry{display:inline;}.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-delete{font-size:12px;font-weight:normal;}.qq-upload-failed-text{display:none;font-style:italic;font-weight:bold;}.qq-upload-failed-icon{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-fail .qq-upload-failed-text{display:inline;}.qq-upload-retrying .qq-upload-failed-text{display:inline;color:#D60000;}.qq-upload-list li.qq-upload-success{background-color:#5DA30C;color:#FFF;}.qq-upload-list li.qq-upload-fail{background-color:#D60000;color:#FFF;}.qq-progress-bar{background:-moz-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,rgba(30,87,153,1)),color-stop(50%,rgba(41,137,216,1)),color-stop(51%,rgba(32,124,202,1)),color-stop(100%,rgba(125,185,232,1)));background:-webkit-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-o-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-ms-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:linear-gradient(to bottom,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);width:0;height:15px;border-radius:6px;margin-bottom:3px;display:none;}
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.4.1/fineuploader.min.js b/ajax/libs/file-uploader/3.4.1/fineuploader.min.js
new file mode 100644
index 000000000..7e53b319a
--- /dev/null
+++ b/ajax/libs/file-uploader/3.4.1/fineuploader.min.js
@@ -0,0 +1,13 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!==null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable!==null&&variable&&typeof variable==="object"&&variable.constructor===Object};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isString=function(maybeString){"use strict";return Object.prototype.toString.call(maybeString)==="[object String]"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFileOrInput=function(maybeFileOrInput){"use strict";if(qq.isBlob(maybeFileOrInput)&&window.File&&maybeFileOrInput instanceof File){return true}else if(window.HTMLInputElement){if(maybeFileOrInput instanceof HTMLInputElement){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}else if(maybeFileOrInput.tagName){if(maybeFileOrInput.tagName.toLowerCase()==="input"){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&maybeBlob instanceof Blob};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice||File.prototype.webkitSlice||File.prototype.mozSlice)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}})};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"misc_data",paramNames:{name:"qqblobname"}},paste:{targetElement:null,defaultName:"pasted_image"}};qq.extend(this._options,o,true);this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._netFilesUploadedOrQueued=0;this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}if(this._options.paste.targetElement){this._pasteHandler=this._createPasteHandler()}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._netFilesUploadedOrQueued++;this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset();this._netFilesUploadedOrQueued=0;if(this._pasteHandler){this._pasteHandler.reset()}},addFiles:function(filesBlobDataOrInputs){var self=this,verifiedFilesOrInputs=[],index,fileOrInput;if(filesBlobDataOrInputs){if(!window.FileList||!(filesBlobDataOrInputs instanceof FileList)){filesBlobDataOrInputs=[].concat(filesBlobDataOrInputs)}for(index=0;index=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||this._options.cors.expected&&(qq.ie10()||!qq.ie()))},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)!==false){this._deleteHandler.sendDelete(id,this.getUuid(id))}}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(fileId){},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);if(isError){this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status,xhr)}else{this._netFilesUploadedOrQueued--;this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){},_onInputChange:function(input){if(qq.isXhrUploadSupported()){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0&&this._netFilesUploadedOrQueued+1>itemLimit){this._itemError("retryFailTooManyItems","");return false}this.log("Retrying upload for '"+fileName+"' (id: "+id+")...");this._filesInProgress.push(id);return true}else{this.log("'"+id+"' is not a valid file ID","error");return false}},_maybeParseAndSendUploadError:function(id,name,response,xhr){if(!response.success){if(xhr&&xhr.status!==200&&!response.error){this._options.callbacks.onError(id,name,"XHR returned response code "+xhr.status,xhr)}else{var errorReason=response.error?response.error:"Upload failure reason unknown";this._options.callbacks.onError(id,name,errorReason,xhr)}}},_uploadFileOrBlobDataList:function(fileOrBlobDataList){var index,validationDescriptors=this._getValidationDescriptors(fileOrBlobDataList),batchValid=this._isBatchValid(validationDescriptors);if(batchValid){if(fileOrBlobDataList.length>0){for(index=0;indexthis._options.validation.sizeLimit){this._itemError("sizeError",name);return false}else if(size&&size99);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,dirPending,droppedFiles=[],droppedEntriesCount=0,droppedEntriesParsedCount=0,disposeSupport=new qq.DisposeSupport;options={dropArea:null,extraDropzones:[],hideDropzones:true,multiple:true,classes:{dropActive:null},callbacks:{dropProcessing:function(isProcessing,files){},error:function(code,filename){},log:function(message,level){}}};
+qq.extend(options,o);function maybeUploadDroppedFiles(){if(droppedEntriesCount===droppedEntriesParsedCount&&!dirPending){options.callbacks.log("Grabbed "+droppedFiles.length+" files after tree traversal.");dz.dropDisabled(false);options.callbacks.dropProcessing(false,droppedFiles)}}function addDroppedFile(file){droppedFiles.push(file);droppedEntriesParsedCount+=1;maybeUploadDroppedFiles()}function traverseFileTree(entry){var dirReader,i;droppedEntriesCount+=1;if(entry.isFile){entry.file(function(file){addDroppedFile(file)})}else if(entry.isDirectory){dirPending=true;dirReader=entry.createReader();dirReader.readEntries(function(entries){droppedEntriesParsedCount+=1;for(i=0;i1&&!options.multiple){options.callbacks.dropProcessing(false);options.callbacks.error("tooManyFilesError","");dz.dropDisabled(false)}else{droppedFiles=[];droppedEntriesCount=0;droppedEntriesParsedCount=0;if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",dropDisabled:"qq-upload-drop-area-disabled",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file"},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false},paste:{promptForName:false,namePromptMessage:"Please name this image"},showMessage:function(message){setTimeout(function(){window.alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=window.confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)},showPrompt:function(message,defaultValue){var promise=new qq.Promise,retVal=window.prompt(message,defaultValue);if(retVal!=null&&qq.trimStr(retVal).length>0){promise.success(retVal)}else{promise.failure("Undefined or invalid user-supplied value.")}return promise}},true);qq.extend(this._options,o,true);this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop();if(this._options.paste.targetElement&&this._options.paste.promptForName){this._setupPastePrompt()}};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeExtraDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop()},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dnd,preventSelectFiles,defaultDropAreaEl;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){defaultDropAreaEl=this._find(this._options.element,"drop")}dnd=new qq.DragAndDrop({dropArea:defaultDropAreaEl,extraDropzones:this._options.dragAndDrop.extraDropzones,hideDropzones:this._options.dragAndDrop.hideDropzones,multiple:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{dropProcessing:function(isProcessing,files){var input=self._button.getInput();if(isProcessing){qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)}else{qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles)}if(files){self.addFiles(files)}},error:function(code,filename){self._itemError(code,filename)},log:function(message,level){self.log(message,level)}}});dnd.setup();return dnd},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){var item=this.getItemByFileId(id);if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}else{qq(item).addClass(this._classes.retryable);return false}},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)!==false){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}}}else{this.log("Delete request ignored for file ID "+id+", delete feature is disabled.","warn");return false}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}this._listElement.appendChild(item);if(this._options.display.fileSizeOnSubmit&&qq.isXhrUploadSupported()){this._displayFileSize(id)}},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=target=target.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.isXhrUploadSupported()){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_itemError:function(code,name){var message=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(message)},_batchError:function(message){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments);this._options.showMessage(message)},_setupPastePrompt:function(){var self=this;this._options.callbacks.onPasteReceived=function(){var message=self._options.paste.namePromptMessage,defaultVal=self._options.paste.defaultName;return self._options.showPrompt(message,defaultVal)}}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,dequeue,handlerImpl;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){}};qq.extend(options,o);log=options.log;dequeue=function(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){log("Cancelling "+id);options.paramsStore.remove(id);handlerImpl.cancel(id);dequeue(id)},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},getQueue:function(){return queue},reset:function(){log("Resetting upload handler");queue=[];handlerImpl.reset()},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}}};qq.UploadHandlerForm=function(o,uploadCompleteCallback,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var id=iframe.id;onloadCallbacks[uuids[id]]=callback;detachLoadEvents[id]=qq(iframe).attach("load",function(){if(inputs[id]){log("Received iframe load event for CORS upload request (file id "+id+")");postMessageCallbackTimers[id]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for file id "+id;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(id,function(message){log("Received the following window message: '"+message+"'");var response=qq.parseJson(message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];detachLoadEvent(id);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(id);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHTML=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHTML);if(innerHTML&&innerHTML.match(/^ ');iframe.setAttribute("id",id);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){if(api.isValid(id)){return inputs[id].value.replace(/.*(\/|\\)/,"")}else{log(id+" is not a valid item ID.","error")}},isValid:function(id){return inputs[id]!==undefined},reset:function(){inputs=[];uuids=[];detachLoadEvents={}},getUuid:function(id){return uuids[id]},cancel:function(id){options.onCancel(id,this.getName(id));delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(id);if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,this.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove();return id}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,logCallback){"use strict";
+var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.isFileChunkingSupported(),resumeEnabled=options.resume.enabled&&chunkFiles&&qq.areCookiesEnabled(),resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);delete fileState[id].xhr;uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(xhr){var response;try{response=qq.parseJson(xhr.responseText)}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function handleFileChunkingUpload(id,retry){var name=api.getName(id),firstChunkIndex=0,persistedChunkInfoForResume,firstChunkDataForResume,currentChunkIndex;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part);if(options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume))!==false){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].uuid=persistedChunkInfoForResume.uuid;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex)}}}for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}}uploadNextChunk(id)}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}api={add:function(fileOrBlobData){var id;if(fileOrBlobData instanceof File){id=fileState.push({file:fileOrBlobData})-1}else if(fileOrBlobData.blob instanceof Blob){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}fileState[id].uuid=qq.getUniqueId();return id},getName:function(id){if(api.isValid(id)){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}}else{log(id+" is not a valid item ID.","error")}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},getLoaded:function(id){return fileState[id].loaded||0},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}},cancel:function(id){var xhr=fileState[id].xhr;options.onCancel(id,this.getName(id));if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.4.1/iframe.xss.response.js b/ajax/libs/file-uploader/3.4.1/iframe.xss.response.js
new file mode 100644
index 000000000..e11fca1e5
--- /dev/null
+++ b/ajax/libs/file-uploader/3.4.1/iframe.xss.response.js
@@ -0,0 +1,6 @@
+(function() {
+ var match = /(\{.+\}).+/.exec(document.body.innerHTML);
+ if (match) {
+ parent.postMessage(match[1], '*');
+ }
+}());
diff --git a/ajax/libs/file-uploader/3.4.1/iframe.xss.response.min.js b/ajax/libs/file-uploader/3.4.1/iframe.xss.response.min.js
new file mode 100644
index 000000000..345cf3603
--- /dev/null
+++ b/ajax/libs/file-uploader/3.4.1/iframe.xss.response.min.js
@@ -0,0 +1 @@
+!function(){var match=/(\{.+\}).+/.exec(document.body.innerHTML);if(match){parent.postMessage(match[1],"*")}}();
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.4.1/loading.gif b/ajax/libs/file-uploader/3.4.1/loading.gif
new file mode 100644
index 000000000..6fba77609
Binary files /dev/null and b/ajax/libs/file-uploader/3.4.1/loading.gif differ
diff --git a/ajax/libs/file-uploader/3.4.1/processing.gif b/ajax/libs/file-uploader/3.4.1/processing.gif
new file mode 100644
index 000000000..7c99504e1
Binary files /dev/null and b/ajax/libs/file-uploader/3.4.1/processing.gif differ
diff --git a/ajax/libs/file-uploader/3.5.0/fineuploader-jquery.js b/ajax/libs/file-uploader/3.5.0/fineuploader-jquery.js
new file mode 100644
index 000000000..4178e1e16
--- /dev/null
+++ b/ajax/libs/file-uploader/3.5.0/fineuploader-jquery.js
@@ -0,0 +1,4488 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity !== null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+ if (window.File && maybeFileOrInput instanceof File) {
+ return true;
+ }
+ else if (window.HTMLInputElement) {
+ if (maybeFileOrInput instanceof HTMLInputElement) {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ else if (maybeFileOrInput.tagName) {
+ if (maybeFileOrInput.tagName.toLowerCase() === 'input') {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && Object.prototype.toString.call(maybeBlob) === '[object Blob]';
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice !== undefined || File.prototype.webkitSlice !== undefined || File.prototype.mozSlice !== undefined);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(obj, callback) {
+ "use strict";
+ var key, retVal;
+ if (obj) {
+ for (key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ retVal = callback(key, obj[key]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ c;
+
+ for(var i=0;i < ca.length;i++) {
+ c = ca[i];
+ while (c.charAt(0)==' ') {
+ c = c.substring(1,c.length);
+ }
+ if (c.indexOf(nameEQ) === 0) {
+ return c.substring(nameEQ.length,c.length);
+ }
+ }
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+qq.supportedFeatures = (function() {
+ var supportsUploading,
+ supportsAjaxFileUploading,
+ supportsFolderDrop,
+ supportsChunking,
+ supportsResume,
+ supportsUploadViaPaste,
+ supportsUploadCors,
+ supportsDeleteFileCors;
+
+
+ function testSupportsFileInputElement() {
+ var supported = true,
+ tempInput;
+
+ try {
+ tempInput = document.createElement('input');
+ tempInput.type = 'file';
+ qq(tempInput).hide();
+
+ if(tempInput.disabled) {
+ supported = false;
+ }
+ }
+ catch(ex) {
+ supported = false;
+ }
+
+ return supported;
+ }
+
+ //only way to test for Filesystem API support since webkit does not expose the DataTransfer interface
+ function isChrome21OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[2][1-9]|Chrome\/[3-9][0-9]/) !== undefined;
+ }
+
+ //only way to test for complete Clipboard API support at this time
+ function isChrome14OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/) !== undefined;
+ }
+
+
+ supportsUploading = testSupportsFileInputElement();
+
+ supportsAjaxFileUploading = supportsUploading && qq.isXhrUploadSupported();
+
+ supportsFolderDrop = supportsAjaxFileUploading && isChrome21OrHigher();
+
+ supportsChunking = supportsAjaxFileUploading && qq.isFileChunkingSupported();
+
+ supportsResume = supportsAjaxFileUploading && supportsChunking && qq.areCookiesEnabled();
+
+ supportsUploadViaPaste = supportsAjaxFileUploading && isChrome14OrHigher();
+
+ supportsUploadCors = supportsUploading && (window.postMessage !== undefined || supportsAjaxFileUploading);
+
+ supportsDeleteFileCors = supportsAjaxFileUploading;
+
+
+ return {
+ uploading: supportsUploading,
+ ajaxUploading: supportsAjaxFileUploading,
+ fileDrop: supportsAjaxFileUploading, //NOTE: will also return true for touch-only devices. It's not currently possible to accurately test for touch-only devices
+ folderDrop: supportsFolderDrop,
+ chunking: supportsChunking,
+ resume: supportsResume,
+ uploadCustomHeaders: supportsAjaxFileUploading,
+ uploadNonMultipart: supportsAjaxFileUploading,
+ itemSizeValidation: supportsAjaxFileUploading,
+ uploadViaPaste: supportsUploadViaPaste,
+ progressBar: supportsAjaxFileUploading,
+ uploadCors: supportsUploadCors,
+ deleteFileCors: supportsDeleteFileCors
+ }
+
+}());
+/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallback, failureCallback, doneCallback,
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ successCallback = onSuccess;
+ failureCallback = onFailure;
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+
+ return this;
+ },
+
+ done: function(callback) {
+ if (state === 0) {
+ doneCallback = callback;
+ }
+ else {
+ callback();
+ }
+
+ return this;
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallback) {
+ successCallback(val);
+ }
+
+ if(doneCallback) {
+ doneCallback();
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallback) {
+ failureCallback(val);
+ }
+
+ if(doneCallback) {
+ doneCallback();
+ }
+
+ return this;
+ }
+ };
+};/*globals qq*/
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ disposeSupport = new qq.DisposeSupport(),
+ options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input) {},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+
+ qq.extend(options, o);
+
+ // make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};qq.FineUploaderBasic = function(o){
+ var that = this;
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON, maybeXhr){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){},
+ onPasteReceived: function(blob) {}
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ defaultResponseError: "Upload failure reason unknown",
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'misc_data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ }
+ };
+
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function() {
+ return this._filesInProgress.length;
+ },
+ getNetUploads: function() {
+ return this._netUploaded;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netUploadedOrQueued++;
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+
+ if (this._pasteHandler) {
+ this._pasteHandler.reset();
+ }
+ },
+ addFiles: function(filesDataOrInputs, params, endpoint) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ index, fileOrInput;
+
+ if (filesDataOrInputs) {
+ if (!window.FileList || !(filesDataOrInputs instanceof FileList)) {
+ filesDataOrInputs = [].concat(filesDataOrInputs);
+ }
+
+ for (index = 0; index < filesDataOrInputs.length; index+=1) {
+ fileOrInput = filesDataOrInputs[index];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
+ this._uploadFileOrBlobDataList(verifiedFilesOrInputs, params, endpoint);
+ }
+ },
+ addBlobs: function(blobDataOrArray, params, endpoint) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._uploadFileOrBlobDataList(verifiedBlobDataList, params, endpoint);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.supportedFeatures.ajaxUploading,
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result, xhr);
+ },
+ onCancel: function(id, name){
+ self._onCancel(id, name);
+ self._options.callbacks.onCancel(id, name);
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ var callback = self._options.callbacks.onPasteReceived,
+ promise = callback(blob);
+
+ if (promise && promise.then) {
+ promise.then(function(successData) {
+ self._handlePasteSuccess(blob, successData);
+ }, function(failureData) {
+ self.log("Ignoring pasted image per paste received callback. Reason = '" + failureData + "'");
+ });
+ }
+ else {
+ self._handlePasteSuccess(blob);
+ }
+ }
+ }
+ });
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total){
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._netUploadedOrQueued--;
+ }
+ else {
+ this._netUploaded++;
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name){
+ this._netUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected || qq.supportedFeatures.deleteFileCors));
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id) !== false) {
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ }
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(fileId) {},
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
+ }
+ else {
+ this._netUploadedOrQueued--;
+ this._netUploaded--;
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name){},
+ _onInputChange: function(input){
+ if (qq.supportedFeatures.ajaxUploading){
+ this.addFiles(input.files);
+ } else {
+ this.addFiles(input);
+ }
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems", "");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : this._options.text.defaultResponseError;
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _uploadFileOrBlobDataList: function(fileOrBlobDataList, params, endpoint) {
+ var index,
+ validationDescriptors = this._getValidationDescriptors(fileOrBlobDataList),
+ batchValid = this._isBatchValid(validationDescriptors);
+
+ if (batchValid) {
+ if (fileOrBlobDataList.length > 0) {
+ for (index = 0; index < fileOrBlobDataList.length; index++){
+ if (this._validateFileOrBlobData(fileOrBlobDataList[index])){
+ this._upload(fileOrBlobDataList[index], params, endpoint);
+ } else {
+ if (this._options.validation.stopOnFirstInvalidFile){
+ return;
+ }
+ }
+ }
+ }
+ else {
+ this._itemError("noFilesError", "");
+ }
+ }
+ },
+ _upload: function(blobOrFileContainer, params, endpoint) {
+ var id = this._handler.add(blobOrFileContainer);
+ var name = this._handler.getName(id);
+
+ if (params) {
+ this.setParams(params, id);
+ }
+
+ if (endpoint) {
+ this.setEndpoint(endpoint, id);
+ }
+
+ if (this._options.callbacks.onSubmit(id, name) !== false) {
+ this._onSubmit(id, name);
+ this._options.callbacks.onSubmitted(id, name);
+
+ if (this._options.autoUpload) {
+ this._handler.upload(id);
+ }
+ else {
+ this._storeForLater(id);
+ }
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _isBatchValid: function(validationDescriptors) {
+ //first, defer the check to the callback (ask the integrator)
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netUploadedOrQueued + validationDescriptors.length,
+ batchValid = this._options.callbacks.onValidateBatch(validationDescriptors) !== false;
+
+ //if the callback hasn't rejected the batch, run some internal tests on the batch next
+ if (batchValid) {
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ batchValid = true;
+ }
+ else {
+ batchValid = false;
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ }
+
+ return batchValid;
+ },
+ _validateFileOrBlobData: function(fileOrBlobData){
+ var validationDescriptor, name, size;
+
+ validationDescriptor = this._getValidationDescriptor(fileOrBlobData);
+ name = validationDescriptor.name;
+ size = validationDescriptor.size;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ return false;
+ }
+
+ if (qq.isFileOrInput(fileOrBlobData) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ return false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ return false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ return false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ return false;
+ }
+
+ return true;
+ },
+ _itemError: function(code, nameOrNames) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ names = [].concat(nameOrNames),
+ name = names[0],
+ extensionsForMessage, placeholderMatch;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ placeholderMatch = message.match(/(\{\w+\})/g);
+ if (placeholderMatch !== null) {
+ qq.each(placeholderMatch, function(idx, placeholder) {
+ r(placeholder, names[idx]);
+ });
+ }
+
+ this._options.callbacks.onError(null, name, message);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1024;
+ i++;
+ } while (bytes > 99);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size !== undefined) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz,
+ droppedFiles = [],
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropZoneElements: [],
+ hideDropZonesBeforeEnter: false,
+ allowMultipleItems: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: new qq.DragAndDrop.callbacks()
+ };
+
+ qq.extend(options, o, true);
+
+ setupDragDrop();
+
+ function uploadDroppedFiles(files) {
+ options.callbacks.dropLog('Grabbed ' + files.length + " dropped files.");
+ dz.dropDisabled(false);
+ options.callbacks.processingDroppedFilesComplete(files);
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i,
+ parseEntryPromise = new qq.Promise();
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ droppedFiles.push(file);
+ parseEntryPromise.success();
+ },
+ function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+ else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ var entriesLeft = entries.length;
+
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]).done(function() {
+ entriesLeft-=1;
+
+ if (entriesLeft === 0) {
+ parseEntryPromise.success();
+ }
+ });
+ }
+
+ if (!entries.length) {
+ parseEntryPromise.success();
+ }
+ }, function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+
+ return parseEntryPromise;
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry,
+ pendingFolderPromises = [],
+ handleDataTransferPromise = new qq.Promise();
+
+ options.callbacks.processingDroppedFiles();
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.allowMultipleItems) {
+ options.callbacks.processingDroppedFilesComplete([]);
+ options.callbacks.dropError('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ handleDataTransferPromise.failure();
+ }
+ else {
+ droppedFiles = [];
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ }
+
+ else {
+ pendingFolderPromises.push(traverseFileTree(entry).done(function() {
+ pendingFolderPromises.pop();
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }));
+ }
+ }
+ }
+ }
+ else {
+ droppedFiles = dataTransfer.files;
+ }
+
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }
+
+ return handleDataTransferPromise;
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer).done(function() {
+ uploadDroppedFiles(droppedFiles);
+ });
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ var dropZones = options.dropZoneElements;
+
+ qq.each(dropZones, function(idx, dropZone) {
+ setupDropzone(dropZone);
+ })
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (dropZones.length && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).css({display: 'block'});
+ });
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropZonesBeforeEnter && qq.FineUploader.prototype._leaving_document_out(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setupExtraDropzone: function(element) {
+ options.dropZoneElements.push(element);
+ setupDropzone(element);
+ },
+
+ removeDropzone: function(element) {
+ var i,
+ dzs = options.dropZoneElements;
+
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+qq.DragAndDrop.callbacks = function() {
+ return {
+ processingDroppedFiles: function() {},
+ processingDroppedFilesComplete: function(files) {},
+ dropError: function(code, errorSpecifics) {
+ qq.log("Drag & drop error code '" + code + " with these specifics: '" + errorSpecifics + "'", "error");
+ },
+ dropLog: function(message, level) {
+ qq.log(message, level);
+ }
+ }
+}
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file",
+ unsupportedBrowser: "Unrecoverable error - this browser does not permit file uploading of any kind."
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+
+ if (!qq.supportedFeatures.uploading || (this._options.cors.expected && !qq.supportedFeatures.uploadCors)) {
+ this._options.element.innerHTML = "" + this._options.messages.unsupportedBrowser + "
"
+ }
+ else {
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dropZoneElements = this._options.dragAndDrop.extraDropzones,
+ preventSelectFiles;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ dropZoneElements.push(this._find(this._options.element, 'drop'));
+ }
+
+ return new qq.DragAndDrop({
+ dropZoneElements: dropZoneElements,
+ hideDropZonesBeforeEnter: this._options.dragAndDrop.hideDropzones,
+ allowMultipleItems: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ processingDroppedFiles: function() {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ },
+ processingDroppedFilesComplete: function(files) {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ dropError: function(code, errorData) {
+ self._itemError(code, errorData);
+ },
+ dropLog: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type){
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name){
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id) !== false) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ }
+ }
+ else {
+ this.log("Delete request ignored for file ID " + id + ", delete feature is disabled.", "warn");
+ return false;
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ this._listElement.appendChild(item);
+
+ if (this._options.display.fileSizeOnSubmit && qq.supportedFeatures.ajaxUploading) {
+ this._displayFileSize(id);
+ }
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = item.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, dequeue, handlerImpl;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ dequeue = function(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.supportedFeatures.ajaxUploading) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, log);
+ }
+
+
+ return {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ return handlerImpl.upload(id);
+ }
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ handlerImpl.cancel(id);
+ dequeue(id);
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ /**
+ * Returns id of files being uploaded or
+ * waiting for their turn
+ */
+ getQueue: function(){
+ return queue;
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ queue = [];
+ handlerImpl.reset();
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var id = iframe.id;
+
+ onloadCallbacks[uuids[id]] = callback;
+
+ detachLoadEvents[id] = qq(iframe).attach('load', function() {
+ if (inputs[id]) {
+ log("Received iframe load event for CORS upload request (file id " + id + ")");
+
+ postMessageCallbackTimers[id] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for file id " + id;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(id, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = qq.parseJson(message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+
+ detachLoadEvent(id);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(id);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHTML = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHTML);
+ //plain text response may be wrapped in tag
+ if (innerHTML && innerHTML.match(/^ ');
+
+ iframe.setAttribute('id', id);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ options.onCancel(id, this.getName(id));
+
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(id);
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ },
+ upload: function(id){
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, this.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+
+ return id;
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.supportedFeatures.chunking,
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.supportedFeatures.resume,
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+
+ options.onComplete(id, name, response, xhr);
+ delete fileState[id].xhr;
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var name = api.getName(id),
+ firstChunkIndex = 0,
+ persistedChunkInfoForResume, firstChunkDataForResume, currentChunkIndex;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part);
+ if (options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume)) !== false) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].uuid = persistedChunkInfoForResume.uuid;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+ }
+ }
+ }
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id;
+
+ if (fileOrBlobData instanceof File) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (qq.isBlob(fileOrBlobData.blob)) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ fileState[id].uuid = qq.getUniqueId();
+ return id;
+ },
+ getName: function(id){
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ /**
+ * Returns uploaded bytes for file identified by id
+ */
+ getLoaded: function(id){
+ return fileState[id].loaded || 0;
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry){
+ var name = this.getName(id);
+
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ },
+ cancel: function(id){
+ var xhr = fileState[id].xhr;
+
+ options.onCancel(id, this.getName(id));
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var uploader, $el, init, dataStore, pluginOption, pluginOptions, addCallbacks, transformVariables, isValidCommand,
+ delegateCommand;
+
+ pluginOptions = ['uploaderType'];
+
+ init = function (options) {
+ if (options) {
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+
+ if (pluginOption('uploaderType') === 'basic') {
+ uploader(new qq.FineUploaderBasic(xformedOpts));
+ }
+ else {
+ uploader(new qq.FineUploader(xformedOpts));
+ }
+ }
+
+ return $el;
+ };
+
+ dataStore = function(key, val) {
+ var data = $el.data('fineuploader');
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data('fineuploader', data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ //the underlying Fine Uploader instance is stored in jQuery's data stored, associated with the element
+ // tied to this instance of the plug-in
+ uploader = function(instanceToStore) {
+ return dataStore('uploader', instanceToStore);
+ };
+
+ pluginOption = function(option, optionVal) {
+ return dataStore(option, optionVal);
+ };
+
+ //implement all callbacks defined in Fine Uploader as functions that trigger appropriately names events and
+ // return the result of executing the bound handler back to Fine Uploader
+ addCallbacks = function(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {},
+ uploaderInst = new qq.FineUploaderBasic();
+
+ $.each(uploaderInst._options.callbacks, function(prop, func) {
+ var name, $callbackEl;
+
+ name = /^on(\w+)/.exec(prop)[1];
+ name = name.substring(0, 1).toLowerCase() + name.substring(1);
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var args = Array.prototype.slice.call(arguments);
+
+ return $callbackEl.triggerHandler(name, args);
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ transformVariables = function(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ if (source.uploaderType !== 'basic') {
+ xformed = { element : $el[0] };
+ }
+ else {
+ xformed = {};
+ }
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if ($.inArray(prop, pluginOptions) >= 0) {
+ pluginOption(prop, val);
+ }
+ else if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ isValidCommand = function(command) {
+ return $.type(command) === "string" &&
+ !command.match(/^_/) && //enforce private methods convention
+ uploader()[command] !== undefined;
+ };
+
+ //assuming we have already verified that this is a valid command, call the associated function in the underlying
+ // Fine Uploader instance (passing along the arguments from the caller) and return the result of the call back to the caller
+ delegateCommand = function(command) {
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
+
+ transformVariables(origArgs, xformedArgs);
+
+ return uploader()[command].apply(uploader(), xformedArgs);
+ };
+
+ $.fn.fineUploader = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (uploader() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error('Method ' + optionsOrCommand + ' does not exist on jQuery.fineUploader');
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
+/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var rootDataKey = "fineUploaderDnd",
+ $el;
+
+ function init (options) {
+ if (!options) {
+ options = {};
+ }
+
+ options.dropZoneElements = [$el];
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+ dnd(new qq.DragAndDrop(xformedOpts));
+
+ return $el;
+ };
+
+ function dataStore(key, val) {
+ var data = $el.data(rootDataKey);
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data(rootDataKey, data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ function dnd(instanceToStore) {
+ return dataStore('dndInstance', instanceToStore);
+ };
+
+ function addCallbacks(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {},
+ dndInst = new qq.FineUploaderBasic();
+
+ $.each(new qq.DragAndDrop.callbacks(), function(prop, func) {
+ var name = prop,
+ $callbackEl;
+
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var args = Array.prototype.slice.call(arguments),
+ jqueryHandlerResult = $callbackEl.triggerHandler(name, args);
+
+ return jqueryHandlerResult;
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ function transformVariables(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ xformed = {};
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ function isValidCommand(command) {
+ return $.type(command) === "string" &&
+ command === "dispose" &&
+ dnd()[command] !== undefined;
+ };
+
+ function delegateCommand(command) {
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
+ transformVariables(origArgs, xformedArgs);
+ return dnd()[command].apply(dnd(), xformedArgs);
+ };
+
+ $.fn.fineUploaderDnd = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (dnd() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error("Method " + optionsOrCommand + " does not exist in Fine Uploader's DnD module.");
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
diff --git a/ajax/libs/file-uploader/3.5.0/fineuploader-jquery.min.js b/ajax/libs/file-uploader/3.5.0/fineuploader-jquery.min.js
new file mode 100644
index 000000000..395e5c8ba
--- /dev/null
+++ b/ajax/libs/file-uploader/3.5.0/fineuploader-jquery.min.js
@@ -0,0 +1,13 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!==null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable!==null&&variable&&typeof variable==="object"&&variable.constructor===Object};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isString=function(maybeString){"use strict";return Object.prototype.toString.call(maybeString)==="[object String]"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFileOrInput=function(maybeFileOrInput){"use strict";if(window.File&&maybeFileOrInput instanceof File){return true}else if(window.HTMLInputElement){if(maybeFileOrInput instanceof HTMLInputElement){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}else if(maybeFileOrInput.tagName){if(maybeFileOrInput.tagName.toLowerCase()==="input"){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&Object.prototype.toString.call(maybeBlob)==="[object Blob]"};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice!==undefined||File.prototype.webkitSlice!==undefined||File.prototype.mozSlice!==undefined)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}})};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{defaultResponseError:"Upload failure reason unknown",sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"misc_data",paramNames:{name:"qqblobname"}},paste:{targetElement:null,defaultName:"pasted_image"}};qq.extend(this._options,o,true);this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._netUploadedOrQueued=0;this._netUploaded=0;this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}if(this._options.paste.targetElement){this._pasteHandler=this._createPasteHandler()}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},getNetUploads:function(){return this._netUploaded},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._netUploadedOrQueued++;this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset();this._netUploadedOrQueued=0;this._netUploaded=0;if(this._pasteHandler){this._pasteHandler.reset()}},addFiles:function(filesDataOrInputs,params,endpoint){var self=this,verifiedFilesOrInputs=[],index,fileOrInput;if(filesDataOrInputs){if(!window.FileList||!(filesDataOrInputs instanceof FileList)){filesDataOrInputs=[].concat(filesDataOrInputs)}for(index=0;index=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||qq.supportedFeatures.deleteFileCors)},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)!==false){this._deleteHandler.sendDelete(id,this.getUuid(id))}}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(fileId){},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);if(isError){this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status,xhr)}else{this._netUploadedOrQueued--;this._netUploaded--;this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){},_onInputChange:function(input){if(qq.supportedFeatures.ajaxUploading){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0&&this._netUploadedOrQueued+1>itemLimit){this._itemError("retryFailTooManyItems","");return false}this.log("Retrying upload for '"+fileName+"' (id: "+id+")...");this._filesInProgress.push(id);return true}else{this.log("'"+id+"' is not a valid file ID","error");return false}},_maybeParseAndSendUploadError:function(id,name,response,xhr){if(!response.success){if(xhr&&xhr.status!==200&&!response.error){this._options.callbacks.onError(id,name,"XHR returned response code "+xhr.status,xhr)}else{var errorReason=response.error?response.error:this._options.text.defaultResponseError;this._options.callbacks.onError(id,name,errorReason,xhr)}}},_uploadFileOrBlobDataList:function(fileOrBlobDataList,params,endpoint){var index,validationDescriptors=this._getValidationDescriptors(fileOrBlobDataList),batchValid=this._isBatchValid(validationDescriptors);if(batchValid){if(fileOrBlobDataList.length>0){for(index=0;indexthis._options.validation.sizeLimit){this._itemError("sizeError",name);return false}else if(size&&size99);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];
+self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size!==undefined){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,droppedFiles=[],disposeSupport=new qq.DisposeSupport;options={dropZoneElements:[],hideDropZonesBeforeEnter:false,allowMultipleItems:true,classes:{dropActive:null},callbacks:new qq.DragAndDrop.callbacks};qq.extend(options,o,true);setupDragDrop();function uploadDroppedFiles(files){options.callbacks.dropLog("Grabbed "+files.length+" dropped files.");dz.dropDisabled(false);options.callbacks.processingDroppedFilesComplete(files)}function traverseFileTree(entry){var dirReader,i,parseEntryPromise=new qq.Promise;if(entry.isFile){entry.file(function(file){droppedFiles.push(file);parseEntryPromise.success()},function(fileError){options.callbacks.dropLog("Problem parsing '"+entry.fullPath+"'. FileError code "+fileError.code+".","error");parseEntryPromise.failure()})}else if(entry.isDirectory){dirReader=entry.createReader();dirReader.readEntries(function(entries){var entriesLeft=entries.length;for(i=0;i1&&!options.allowMultipleItems){options.callbacks.processingDroppedFilesComplete([]);options.callbacks.dropError("tooManyFilesError","");dz.dropDisabled(false);handleDataTransferPromise.failure()}else{droppedFiles=[];if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file",unsupportedBrowser:"Unrecoverable error - this browser does not permit file uploading of any kind."},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false},paste:{promptForName:false,namePromptMessage:"Please name this image"},showMessage:function(message){setTimeout(function(){window.alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=window.confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)},showPrompt:function(message,defaultValue){var promise=new qq.Promise,retVal=window.prompt(message,defaultValue);if(retVal!=null&&qq.trimStr(retVal).length>0){promise.success(retVal)}else{promise.failure("Undefined or invalid user-supplied value.")}return promise}},true);qq.extend(this._options,o,true);if(!qq.supportedFeatures.uploading||this._options.cors.expected&&!qq.supportedFeatures.uploadCors){this._options.element.innerHTML=""+this._options.messages.unsupportedBrowser+"
"}else{this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop();if(this._options.paste.targetElement&&this._options.paste.promptForName){this._setupPastePrompt()}}};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop()},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dropZoneElements=this._options.dragAndDrop.extraDropzones,preventSelectFiles;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){dropZoneElements.push(this._find(this._options.element,"drop"))}return new qq.DragAndDrop({dropZoneElements:dropZoneElements,hideDropZonesBeforeEnter:this._options.dragAndDrop.hideDropzones,allowMultipleItems:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{processingDroppedFiles:function(){var input=self._button.getInput();qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)},processingDroppedFilesComplete:function(files){var input=self._button.getInput();qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles);if(files){self.addFiles(files)}},dropError:function(code,errorData){self._itemError(code,errorData)},dropLog:function(message,level){self.log(message,level)}}})},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){var item=this.getItemByFileId(id);if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}else{qq(item).addClass(this._classes.retryable);return false}},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)!==false){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}}}else{this.log("Delete request ignored for file ID "+id+", delete feature is disabled.","warn");return false}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}this._listElement.appendChild(item);if(this._options.display.fileSizeOnSubmit&&qq.supportedFeatures.ajaxUploading){this._displayFileSize(id)}},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=item.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_itemError:function(code,name){var message=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(message)},_batchError:function(message){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments);this._options.showMessage(message)},_setupPastePrompt:function(){var self=this;this._options.callbacks.onPasteReceived=function(){var message=self._options.paste.namePromptMessage,defaultVal=self._options.paste.defaultName;return self._options.showPrompt(message,defaultVal)}}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,dequeue,handlerImpl;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){}};qq.extend(options,o);log=options.log;dequeue=function(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){log("Cancelling "+id);options.paramsStore.remove(id);handlerImpl.cancel(id);dequeue(id)},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},getQueue:function(){return queue},reset:function(){log("Resetting upload handler");queue=[];handlerImpl.reset()},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}}};qq.UploadHandlerForm=function(o,uploadCompleteCallback,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var id=iframe.id;onloadCallbacks[uuids[id]]=callback;detachLoadEvents[id]=qq(iframe).attach("load",function(){if(inputs[id]){log("Received iframe load event for CORS upload request (file id "+id+")");postMessageCallbackTimers[id]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for file id "+id;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(id,function(message){log("Received the following window message: '"+message+"'");var response=qq.parseJson(message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];detachLoadEvent(id);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(id);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)
+}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHTML=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHTML);if(innerHTML&&innerHTML.match(/^ ');iframe.setAttribute("id",id);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){if(api.isValid(id)){return inputs[id].value.replace(/.*(\/|\\)/,"")}else{log(id+" is not a valid item ID.","error")}},isValid:function(id){return inputs[id]!==undefined},reset:function(){inputs=[];uuids=[];detachLoadEvents={}},getUuid:function(id){return uuids[id]},cancel:function(id){options.onCancel(id,this.getName(id));delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(id);if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,this.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove();return id}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,logCallback){"use strict";var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.supportedFeatures.chunking,resumeEnabled=options.resume.enabled&&chunkFiles&&qq.supportedFeatures.resume,resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);delete fileState[id].xhr;uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(xhr){var response;try{response=qq.parseJson(xhr.responseText)}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function handleFileChunkingUpload(id,retry){var name=api.getName(id),firstChunkIndex=0,persistedChunkInfoForResume,firstChunkDataForResume,currentChunkIndex;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part);if(options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume))!==false){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].uuid=persistedChunkInfoForResume.uuid;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex)}}}for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}}uploadNextChunk(id)}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}api={add:function(fileOrBlobData){var id;if(fileOrBlobData instanceof File){id=fileState.push({file:fileOrBlobData})-1}else if(qq.isBlob(fileOrBlobData.blob)){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}fileState[id].uuid=qq.getUniqueId();return id},getName:function(id){if(api.isValid(id)){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}}else{log(id+" is not a valid item ID.","error")}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},getLoaded:function(id){return fileState[id].loaded||0},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}},cancel:function(id){var xhr=fileState[id].xhr;options.onCancel(id,this.getName(id));if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};!function($){"use strict";var uploader,$el,init,dataStore,pluginOption,pluginOptions,addCallbacks,transformVariables,isValidCommand,delegateCommand;pluginOptions=["uploaderType"];init=function(options){if(options){var xformedOpts=transformVariables(options);addCallbacks(xformedOpts);if(pluginOption("uploaderType")==="basic"){uploader(new qq.FineUploaderBasic(xformedOpts))}else{uploader(new qq.FineUploader(xformedOpts))}}return $el};dataStore=function(key,val){var data=$el.data("fineuploader");if(val){if(data===undefined){data={}}data[key]=val;$el.data("fineuploader",data)}else{if(data===undefined){return null}return data[key]}};uploader=function(instanceToStore){return dataStore("uploader",instanceToStore)};pluginOption=function(option,optionVal){return dataStore(option,optionVal)};addCallbacks=function(transformedOpts){var callbacks=transformedOpts.callbacks={},uploaderInst=new qq.FineUploaderBasic;$.each(uploaderInst._options.callbacks,function(prop,func){var name,$callbackEl;name=/^on(\w+)/.exec(prop)[1];name=name.substring(0,1).toLowerCase()+name.substring(1);$callbackEl=$el;callbacks[prop]=function(){var args=Array.prototype.slice.call(arguments);return $callbackEl.triggerHandler(name,args)}})};transformVariables=function(source,dest){var xformed,arrayVals;if(dest===undefined){if(source.uploaderType!=="basic"){xformed={element:$el[0]}}else{xformed={}}}else{xformed=dest}$.each(source,function(prop,val){if($.inArray(prop,pluginOptions)>=0){pluginOption(prop,val)}else if(val instanceof $){xformed[prop]=val[0]}else if($.isPlainObject(val)){xformed[prop]={};transformVariables(val,xformed[prop])}else if($.isArray(val)){arrayVals=[];$.each(val,function(idx,arrayVal){if(arrayVal instanceof $){$.merge(arrayVals,arrayVal)}else{arrayVals.push(arrayVal)}});xformed[prop]=arrayVals}else{xformed[prop]=val}});if(dest===undefined){return xformed}};isValidCommand=function(command){return $.type(command)==="string"&&!command.match(/^_/)&&uploader()[command]!==undefined};delegateCommand=function(command){var xformedArgs=[],origArgs=Array.prototype.slice.call(arguments,1);transformVariables(origArgs,xformedArgs);return uploader()[command].apply(uploader(),xformedArgs)};$.fn.fineUploader=function(optionsOrCommand){var self=this,selfArgs=arguments,retVals=[];this.each(function(index,el){$el=$(el);if(uploader()&&isValidCommand(optionsOrCommand)){retVals.push(delegateCommand.apply(self,selfArgs));if(self.length===1){return false}}else if(typeof optionsOrCommand==="object"||!optionsOrCommand){init.apply(self,selfArgs)}else{$.error("Method "+optionsOrCommand+" does not exist on jQuery.fineUploader")}});if(retVals.length===1){return retVals[0]}else if(retVals.length>1){return retVals}return this}}(jQuery);!function($){"use strict";var rootDataKey="fineUploaderDnd",$el;function init(options){if(!options){options={}}options.dropZoneElements=[$el];var xformedOpts=transformVariables(options);addCallbacks(xformedOpts);dnd(new qq.DragAndDrop(xformedOpts));return $el}function dataStore(key,val){var data=$el.data(rootDataKey);if(val){if(data===undefined){data={}}data[key]=val;$el.data(rootDataKey,data)}else{if(data===undefined){return null}return data[key]}}function dnd(instanceToStore){return dataStore("dndInstance",instanceToStore)}function addCallbacks(transformedOpts){var callbacks=transformedOpts.callbacks={},dndInst=new qq.FineUploaderBasic;$.each(new qq.DragAndDrop.callbacks,function(prop,func){var name=prop,$callbackEl;$callbackEl=$el;callbacks[prop]=function(){var args=Array.prototype.slice.call(arguments),jqueryHandlerResult=$callbackEl.triggerHandler(name,args);return jqueryHandlerResult}})}function transformVariables(source,dest){var xformed,arrayVals;if(dest===undefined){xformed={}}else{xformed=dest}$.each(source,function(prop,val){if(val instanceof $){xformed[prop]=val[0]}else if($.isPlainObject(val)){xformed[prop]={};transformVariables(val,xformed[prop])}else if($.isArray(val)){arrayVals=[];$.each(val,function(idx,arrayVal){if(arrayVal instanceof $){$.merge(arrayVals,arrayVal)}else{arrayVals.push(arrayVal)}});xformed[prop]=arrayVals}else{xformed[prop]=val}});if(dest===undefined){return xformed}}function isValidCommand(command){return $.type(command)==="string"&&command==="dispose"&&dnd()[command]!==undefined}function delegateCommand(command){var xformedArgs=[],origArgs=Array.prototype.slice.call(arguments,1);transformVariables(origArgs,xformedArgs);return dnd()[command].apply(dnd(),xformedArgs)}$.fn.fineUploaderDnd=function(optionsOrCommand){var self=this,selfArgs=arguments,retVals=[];this.each(function(index,el){$el=$(el);if(dnd()&&isValidCommand(optionsOrCommand)){retVals.push(delegateCommand.apply(self,selfArgs));if(self.length===1){return false}}else if(typeof optionsOrCommand==="object"||!optionsOrCommand){init.apply(self,selfArgs)}else{$.error("Method "+optionsOrCommand+" does not exist in Fine Uploader's DnD module.")}});if(retVals.length===1){return retVals[0]}else if(retVals.length>1){return retVals}return this}}(jQuery);
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.5.0/fineuploader.css b/ajax/libs/file-uploader/3.5.0/fineuploader.css
new file mode 100644
index 000000000..11a436a06
--- /dev/null
+++ b/ajax/libs/file-uploader/3.5.0/fineuploader.css
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2013, Widen Enterprises info@fineuploader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+.qq-uploader {
+ position: relative;
+ width: 100%;
+}
+.qq-upload-button {
+ display: block;
+ width: 105px;
+ padding: 7px 0;
+ text-align: center;
+ background: #880000;
+ border-bottom: 1px solid #DDD;
+ color: #FFF;
+}
+.qq-upload-button-hover {
+ background: #CC0000;
+}
+.qq-upload-button-focus {
+ outline: 1px dotted #000000;
+}
+.qq-upload-drop-area, .qq-upload-extra-drop-area {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ min-height: 30px;
+ z-index: 2;
+ background: #FF9797;
+ text-align: center;
+}
+.qq-upload-drop-area span {
+ display: block;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ margin-top: -8px;
+ font-size: 16px;
+}
+.qq-upload-extra-drop-area {
+ position: relative;
+ margin-top: 50px;
+ font-size: 16px;
+ padding-top: 30px;
+ height: 20px;
+ min-height: 40px;
+}
+.qq-upload-drop-area-active {
+ background: #FF7171;
+}
+.qq-upload-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+.qq-upload-list li {
+ margin: 0;
+ padding: 9px;
+ line-height: 15px;
+ font-size: 16px;
+ background-color: #FFF0BD;
+}
+.qq-upload-file, .qq-upload-spinner, .qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text, .qq-upload-finished, .qq-upload-delete {
+ margin-right: 12px;
+}
+.qq-upload-file {
+}
+.qq-upload-spinner {
+ display: inline-block;
+ background: url("loading.gif");
+ width: 15px;
+ height: 15px;
+ vertical-align: text-bottom;
+}
+.qq-drop-processing {
+ display: none;
+}
+.qq-drop-processing-spinner {
+ display: inline-block;
+ background: url("processing.gif");
+ width: 24px;
+ height: 24px;
+ vertical-align: text-bottom;
+}
+.qq-upload-finished {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-retry, .qq-upload-delete {
+ display: none;
+ color: #000000;
+}
+.qq-upload-cancel, .qq-upload-delete {
+ color: #000000;
+}
+.qq-upload-retryable .qq-upload-retry {
+ display: inline;
+}
+.qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-delete {
+ font-size: 12px;
+ font-weight: normal;
+}
+.qq-upload-failed-text {
+ display: none;
+ font-style: italic;
+ font-weight: bold;
+}
+.qq-upload-failed-icon {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-fail .qq-upload-failed-text {
+ display: inline;
+}
+.qq-upload-retrying .qq-upload-failed-text {
+ display: inline;
+ color: #D60000;
+}
+.qq-upload-list li.qq-upload-success {
+ background-color: #5DA30C;
+ color: #FFFFFF;
+}
+.qq-upload-list li.qq-upload-fail {
+ background-color: #D60000;
+ color: #FFFFFF;
+}
+.qq-progress-bar {
+ background: -moz-linear-gradient(top, rgba(30,87,153,1) 0%, rgba(41,137,216,1) 50%, rgba(32,124,202,1) 51%, rgba(125,185,232,1) 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(30,87,153,1)), color-stop(50%,rgba(41,137,216,1)), color-stop(51%,rgba(32,124,202,1)), color-stop(100%,rgba(125,185,232,1))); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* IE10+ */
+ background: linear-gradient(to bottom, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* W3C */
+ width: 0%;
+ height: 15px;
+ border-radius: 6px;
+ margin-bottom: 3px;
+ display: none;
+}
diff --git a/ajax/libs/file-uploader/3.5.0/fineuploader.js b/ajax/libs/file-uploader/3.5.0/fineuploader.js
new file mode 100644
index 000000000..cc5094fbd
--- /dev/null
+++ b/ajax/libs/file-uploader/3.5.0/fineuploader.js
@@ -0,0 +1,4173 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity !== null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+ if (window.File && maybeFileOrInput instanceof File) {
+ return true;
+ }
+ else if (window.HTMLInputElement) {
+ if (maybeFileOrInput instanceof HTMLInputElement) {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ else if (maybeFileOrInput.tagName) {
+ if (maybeFileOrInput.tagName.toLowerCase() === 'input') {
+ if (maybeFileOrInput.type && maybeFileOrInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && Object.prototype.toString.call(maybeBlob) === '[object Blob]';
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice !== undefined || File.prototype.webkitSlice !== undefined || File.prototype.mozSlice !== undefined);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(obj, callback) {
+ "use strict";
+ var key, retVal;
+ if (obj) {
+ for (key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ retVal = callback(key, obj[key]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ c;
+
+ for(var i=0;i < ca.length;i++) {
+ c = ca[i];
+ while (c.charAt(0)==' ') {
+ c = c.substring(1,c.length);
+ }
+ if (c.indexOf(nameEQ) === 0) {
+ return c.substring(nameEQ.length,c.length);
+ }
+ }
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+qq.supportedFeatures = (function() {
+ var supportsUploading,
+ supportsAjaxFileUploading,
+ supportsFolderDrop,
+ supportsChunking,
+ supportsResume,
+ supportsUploadViaPaste,
+ supportsUploadCors,
+ supportsDeleteFileCors;
+
+
+ function testSupportsFileInputElement() {
+ var supported = true,
+ tempInput;
+
+ try {
+ tempInput = document.createElement('input');
+ tempInput.type = 'file';
+ qq(tempInput).hide();
+
+ if(tempInput.disabled) {
+ supported = false;
+ }
+ }
+ catch(ex) {
+ supported = false;
+ }
+
+ return supported;
+ }
+
+ //only way to test for Filesystem API support since webkit does not expose the DataTransfer interface
+ function isChrome21OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[2][1-9]|Chrome\/[3-9][0-9]/) !== undefined;
+ }
+
+ //only way to test for complete Clipboard API support at this time
+ function isChrome14OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/) !== undefined;
+ }
+
+
+ supportsUploading = testSupportsFileInputElement();
+
+ supportsAjaxFileUploading = supportsUploading && qq.isXhrUploadSupported();
+
+ supportsFolderDrop = supportsAjaxFileUploading && isChrome21OrHigher();
+
+ supportsChunking = supportsAjaxFileUploading && qq.isFileChunkingSupported();
+
+ supportsResume = supportsAjaxFileUploading && supportsChunking && qq.areCookiesEnabled();
+
+ supportsUploadViaPaste = supportsAjaxFileUploading && isChrome14OrHigher();
+
+ supportsUploadCors = supportsUploading && (window.postMessage !== undefined || supportsAjaxFileUploading);
+
+ supportsDeleteFileCors = supportsAjaxFileUploading;
+
+
+ return {
+ uploading: supportsUploading,
+ ajaxUploading: supportsAjaxFileUploading,
+ fileDrop: supportsAjaxFileUploading, //NOTE: will also return true for touch-only devices. It's not currently possible to accurately test for touch-only devices
+ folderDrop: supportsFolderDrop,
+ chunking: supportsChunking,
+ resume: supportsResume,
+ uploadCustomHeaders: supportsAjaxFileUploading,
+ uploadNonMultipart: supportsAjaxFileUploading,
+ itemSizeValidation: supportsAjaxFileUploading,
+ uploadViaPaste: supportsUploadViaPaste,
+ progressBar: supportsAjaxFileUploading,
+ uploadCors: supportsUploadCors,
+ deleteFileCors: supportsDeleteFileCors
+ }
+
+}());
+/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallback, failureCallback, doneCallback,
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ successCallback = onSuccess;
+ failureCallback = onFailure;
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+
+ return this;
+ },
+
+ done: function(callback) {
+ if (state === 0) {
+ doneCallback = callback;
+ }
+ else {
+ callback();
+ }
+
+ return this;
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallback) {
+ successCallback(val);
+ }
+
+ if(doneCallback) {
+ doneCallback();
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallback) {
+ failureCallback(val);
+ }
+
+ if(doneCallback) {
+ doneCallback();
+ }
+
+ return this;
+ }
+ };
+};/*globals qq*/
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ disposeSupport = new qq.DisposeSupport(),
+ options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input) {},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+
+ qq.extend(options, o);
+
+ // make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};qq.FineUploaderBasic = function(o){
+ var that = this;
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON, maybeXhr){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){},
+ onPasteReceived: function(blob) {}
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ defaultResponseError: "Upload failure reason unknown",
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'misc_data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ }
+ };
+
+ qq.extend(this._options, o, true);
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function() {
+ return this._filesInProgress.length;
+ },
+ getNetUploads: function() {
+ return this._netUploaded;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netUploadedOrQueued++;
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+
+ if (this._pasteHandler) {
+ this._pasteHandler.reset();
+ }
+ },
+ addFiles: function(filesDataOrInputs, params, endpoint) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ index, fileOrInput;
+
+ if (filesDataOrInputs) {
+ if (!window.FileList || !(filesDataOrInputs instanceof FileList)) {
+ filesDataOrInputs = [].concat(filesDataOrInputs);
+ }
+
+ for (index = 0; index < filesDataOrInputs.length; index+=1) {
+ fileOrInput = filesDataOrInputs[index];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Processing ' + verifiedFilesOrInputs.length + ' files or inputs...');
+ this._uploadFileOrBlobDataList(verifiedFilesOrInputs, params, endpoint);
+ }
+ },
+ addBlobs: function(blobDataOrArray, params, endpoint) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._uploadFileOrBlobDataList(verifiedBlobDataList, params, endpoint);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.supportedFeatures.ajaxUploading,
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result, xhr);
+ },
+ onCancel: function(id, name){
+ self._onCancel(id, name);
+ self._options.callbacks.onCancel(id, name);
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ var callback = self._options.callbacks.onPasteReceived,
+ promise = callback(blob);
+
+ if (promise && promise.then) {
+ promise.then(function(successData) {
+ self._handlePasteSuccess(blob, successData);
+ }, function(failureData) {
+ self.log("Ignoring pasted image per paste received callback. Reason = '" + failureData + "'");
+ });
+ }
+ else {
+ self._handlePasteSuccess(blob);
+ }
+ }
+ }
+ });
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total){
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._netUploadedOrQueued--;
+ }
+ else {
+ this._netUploaded++;
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name){
+ this._netUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected || qq.supportedFeatures.deleteFileCors));
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id) !== false) {
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ }
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(fileId) {},
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
+ }
+ else {
+ this._netUploadedOrQueued--;
+ this._netUploaded--;
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name){},
+ _onInputChange: function(input){
+ if (qq.supportedFeatures.ajaxUploading){
+ this.addFiles(input.files);
+ } else {
+ this.addFiles(input);
+ }
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems", "");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : this._options.text.defaultResponseError;
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _uploadFileOrBlobDataList: function(fileOrBlobDataList, params, endpoint) {
+ var index,
+ validationDescriptors = this._getValidationDescriptors(fileOrBlobDataList),
+ batchValid = this._isBatchValid(validationDescriptors);
+
+ if (batchValid) {
+ if (fileOrBlobDataList.length > 0) {
+ for (index = 0; index < fileOrBlobDataList.length; index++){
+ if (this._validateFileOrBlobData(fileOrBlobDataList[index])){
+ this._upload(fileOrBlobDataList[index], params, endpoint);
+ } else {
+ if (this._options.validation.stopOnFirstInvalidFile){
+ return;
+ }
+ }
+ }
+ }
+ else {
+ this._itemError("noFilesError", "");
+ }
+ }
+ },
+ _upload: function(blobOrFileContainer, params, endpoint) {
+ var id = this._handler.add(blobOrFileContainer);
+ var name = this._handler.getName(id);
+
+ if (params) {
+ this.setParams(params, id);
+ }
+
+ if (endpoint) {
+ this.setEndpoint(endpoint, id);
+ }
+
+ if (this._options.callbacks.onSubmit(id, name) !== false) {
+ this._onSubmit(id, name);
+ this._options.callbacks.onSubmitted(id, name);
+
+ if (this._options.autoUpload) {
+ this._handler.upload(id);
+ }
+ else {
+ this._storeForLater(id);
+ }
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _isBatchValid: function(validationDescriptors) {
+ //first, defer the check to the callback (ask the integrator)
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netUploadedOrQueued + validationDescriptors.length,
+ batchValid = this._options.callbacks.onValidateBatch(validationDescriptors) !== false;
+
+ //if the callback hasn't rejected the batch, run some internal tests on the batch next
+ if (batchValid) {
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ batchValid = true;
+ }
+ else {
+ batchValid = false;
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ }
+
+ return batchValid;
+ },
+ _validateFileOrBlobData: function(fileOrBlobData){
+ var validationDescriptor, name, size;
+
+ validationDescriptor = this._getValidationDescriptor(fileOrBlobData);
+ name = validationDescriptor.name;
+ size = validationDescriptor.size;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ return false;
+ }
+
+ if (qq.isFileOrInput(fileOrBlobData) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ return false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ return false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ return false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ return false;
+ }
+
+ return true;
+ },
+ _itemError: function(code, nameOrNames) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ names = [].concat(nameOrNames),
+ name = names[0],
+ extensionsForMessage, placeholderMatch;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ placeholderMatch = message.match(/(\{\w+\})/g);
+ if (placeholderMatch !== null) {
+ qq.each(placeholderMatch, function(idx, placeholder) {
+ r(placeholder, names[idx]);
+ });
+ }
+
+ this._options.callbacks.onError(null, name, message);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1024;
+ i++;
+ } while (bytes > 99);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size !== undefined) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz,
+ droppedFiles = [],
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropZoneElements: [],
+ hideDropZonesBeforeEnter: false,
+ allowMultipleItems: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: new qq.DragAndDrop.callbacks()
+ };
+
+ qq.extend(options, o, true);
+
+ setupDragDrop();
+
+ function uploadDroppedFiles(files) {
+ options.callbacks.dropLog('Grabbed ' + files.length + " dropped files.");
+ dz.dropDisabled(false);
+ options.callbacks.processingDroppedFilesComplete(files);
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i,
+ parseEntryPromise = new qq.Promise();
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ droppedFiles.push(file);
+ parseEntryPromise.success();
+ },
+ function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+ else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ var entriesLeft = entries.length;
+
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]).done(function() {
+ entriesLeft-=1;
+
+ if (entriesLeft === 0) {
+ parseEntryPromise.success();
+ }
+ });
+ }
+
+ if (!entries.length) {
+ parseEntryPromise.success();
+ }
+ }, function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+
+ return parseEntryPromise;
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry,
+ pendingFolderPromises = [],
+ handleDataTransferPromise = new qq.Promise();
+
+ options.callbacks.processingDroppedFiles();
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.allowMultipleItems) {
+ options.callbacks.processingDroppedFilesComplete([]);
+ options.callbacks.dropError('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ handleDataTransferPromise.failure();
+ }
+ else {
+ droppedFiles = [];
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ }
+
+ else {
+ pendingFolderPromises.push(traverseFileTree(entry).done(function() {
+ pendingFolderPromises.pop();
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }));
+ }
+ }
+ }
+ }
+ else {
+ droppedFiles = dataTransfer.files;
+ }
+
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }
+
+ return handleDataTransferPromise;
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer).done(function() {
+ uploadDroppedFiles(droppedFiles);
+ });
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ var dropZones = options.dropZoneElements;
+
+ qq.each(dropZones, function(idx, dropZone) {
+ setupDropzone(dropZone);
+ })
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (dropZones.length && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).css({display: 'block'});
+ });
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropZonesBeforeEnter && qq.FineUploader.prototype._leaving_document_out(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setupExtraDropzone: function(element) {
+ options.dropZoneElements.push(element);
+ setupDropzone(element);
+ },
+
+ removeDropzone: function(element) {
+ var i,
+ dzs = options.dropZoneElements;
+
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+qq.DragAndDrop.callbacks = function() {
+ return {
+ processingDroppedFiles: function() {},
+ processingDroppedFilesComplete: function(files) {},
+ dropError: function(code, errorSpecifics) {
+ qq.log("Drag & drop error code '" + code + " with these specifics: '" + errorSpecifics + "'", "error");
+ },
+ dropLog: function(message, level) {
+ qq.log(message, level);
+ }
+ }
+}
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file",
+ unsupportedBrowser: "Unrecoverable error - this browser does not permit file uploading of any kind."
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+
+ if (!qq.supportedFeatures.uploading || (this._options.cors.expected && !qq.supportedFeatures.uploadCors)) {
+ this._options.element.innerHTML = "" + this._options.messages.unsupportedBrowser + "
"
+ }
+ else {
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dropZoneElements = this._options.dragAndDrop.extraDropzones,
+ preventSelectFiles;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ dropZoneElements.push(this._find(this._options.element, 'drop'));
+ }
+
+ return new qq.DragAndDrop({
+ dropZoneElements: dropZoneElements,
+ hideDropZonesBeforeEnter: this._options.dragAndDrop.hideDropzones,
+ allowMultipleItems: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ processingDroppedFiles: function() {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ },
+ processingDroppedFilesComplete: function(files) {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ dropError: function(code, errorData) {
+ self._itemError(code, errorData);
+ },
+ dropLog: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type){
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name){
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ if (this._isDeletePossible()) {
+ if (this._options.callbacks.onSubmitDelete(id) !== false) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ }
+ }
+ else {
+ this.log("Delete request ignored for file ID " + id + ", delete feature is disabled.", "warn");
+ return false;
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ this._listElement.appendChild(item);
+
+ if (this._options.display.fileSizeOnSubmit && qq.supportedFeatures.ajaxUploading) {
+ this._displayFileSize(id);
+ }
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = item.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, dequeue, handlerImpl;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ dequeue = function(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.supportedFeatures.ajaxUploading) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, log);
+ }
+
+
+ return {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ return handlerImpl.upload(id);
+ }
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ handlerImpl.cancel(id);
+ dequeue(id);
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ /**
+ * Returns id of files being uploaded or
+ * waiting for their turn
+ */
+ getQueue: function(){
+ return queue;
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ queue = [];
+ handlerImpl.reset();
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var id = iframe.id;
+
+ onloadCallbacks[uuids[id]] = callback;
+
+ detachLoadEvents[id] = qq(iframe).attach('load', function() {
+ if (inputs[id]) {
+ log("Received iframe load event for CORS upload request (file id " + id + ")");
+
+ postMessageCallbackTimers[id] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for file id " + id;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(id, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = qq.parseJson(message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+
+ detachLoadEvent(id);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(id);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHTML = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHTML);
+ //plain text response may be wrapped in tag
+ if (innerHTML && innerHTML.match(/^ ');
+
+ iframe.setAttribute('id', id);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ options.onCancel(id, this.getName(id));
+
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(id);
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ },
+ upload: function(id){
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, this.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+
+ return id;
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.supportedFeatures.chunking,
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.supportedFeatures.resume,
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+
+ options.onComplete(id, name, response, xhr);
+ delete fileState[id].xhr;
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var name = api.getName(id),
+ firstChunkIndex = 0,
+ persistedChunkInfoForResume, firstChunkDataForResume, currentChunkIndex;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part);
+ if (options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume)) !== false) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].uuid = persistedChunkInfoForResume.uuid;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+ }
+ }
+ }
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id;
+
+ if (fileOrBlobData instanceof File) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (qq.isBlob(fileOrBlobData.blob)) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ fileState[id].uuid = qq.getUniqueId();
+ return id;
+ },
+ getName: function(id){
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ /**
+ * Returns uploaded bytes for file identified by id
+ */
+ getLoaded: function(id){
+ return fileState[id].loaded || 0;
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry){
+ var name = this.getName(id);
+
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ },
+ cancel: function(id){
+ var xhr = fileState[id].xhr;
+
+ options.onCancel(id, this.getName(id));
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
diff --git a/ajax/libs/file-uploader/3.5.0/fineuploader.min.css b/ajax/libs/file-uploader/3.5.0/fineuploader.min.css
new file mode 100644
index 000000000..110e8fa52
--- /dev/null
+++ b/ajax/libs/file-uploader/3.5.0/fineuploader.min.css
@@ -0,0 +1 @@
+.qq-uploader{position:relative;width:100%;}.qq-upload-button{display:block;width:105px;padding:7px 0;text-align:center;background:#800;border-bottom:1px solid #DDD;color:#FFF;}.qq-upload-button-hover{background:#C00;}.qq-upload-button-focus{outline:1px dotted #000;}.qq-upload-drop-area,.qq-upload-extra-drop-area{position:absolute;top:0;left:0;width:100%;height:100%;min-height:30px;z-index:2;background:#FF9797;text-align:center;}.qq-upload-drop-area span{display:block;position:absolute;top:50%;width:100%;margin-top:-8px;font-size:16px;}.qq-upload-extra-drop-area{position:relative;margin-top:50px;font-size:16px;padding-top:30px;height:20px;min-height:40px;}.qq-upload-drop-area-active{background:#FF7171;}.qq-upload-list{margin:0;padding:0;list-style:none;}.qq-upload-list li{margin:0;padding:9px;line-height:15px;font-size:16px;background-color:#FFF0BD;}.qq-upload-file,.qq-upload-spinner,.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-failed-text,.qq-upload-finished,.qq-upload-delete{margin-right:12px;}.qq-upload-spinner{display:inline-block;background:url("loading.gif");width:15px;height:15px;vertical-align:text-bottom;}.qq-drop-processing{display:none;}.qq-drop-processing-spinner{display:inline-block;background:url("processing.gif");width:24px;height:24px;vertical-align:text-bottom;}.qq-upload-finished{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-retry,.qq-upload-delete{display:none;color:#000;}.qq-upload-cancel,.qq-upload-delete{color:#000;}.qq-upload-retryable .qq-upload-retry{display:inline;}.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-delete{font-size:12px;font-weight:normal;}.qq-upload-failed-text{display:none;font-style:italic;font-weight:bold;}.qq-upload-failed-icon{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-fail .qq-upload-failed-text{display:inline;}.qq-upload-retrying .qq-upload-failed-text{display:inline;color:#D60000;}.qq-upload-list li.qq-upload-success{background-color:#5DA30C;color:#FFF;}.qq-upload-list li.qq-upload-fail{background-color:#D60000;color:#FFF;}.qq-progress-bar{background:-moz-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,rgba(30,87,153,1)),color-stop(50%,rgba(41,137,216,1)),color-stop(51%,rgba(32,124,202,1)),color-stop(100%,rgba(125,185,232,1)));background:-webkit-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-o-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-ms-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:linear-gradient(to bottom,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);width:0;height:15px;border-radius:6px;margin-bottom:3px;display:none;}
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.5.0/fineuploader.min.js b/ajax/libs/file-uploader/3.5.0/fineuploader.min.js
new file mode 100644
index 000000000..f67d8662b
--- /dev/null
+++ b/ajax/libs/file-uploader/3.5.0/fineuploader.min.js
@@ -0,0 +1,13 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!==null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable!==null&&variable&&typeof variable==="object"&&variable.constructor===Object};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isString=function(maybeString){"use strict";return Object.prototype.toString.call(maybeString)==="[object String]"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFileOrInput=function(maybeFileOrInput){"use strict";if(window.File&&maybeFileOrInput instanceof File){return true}else if(window.HTMLInputElement){if(maybeFileOrInput instanceof HTMLInputElement){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}else if(maybeFileOrInput.tagName){if(maybeFileOrInput.tagName.toLowerCase()==="input"){if(maybeFileOrInput.type&&maybeFileOrInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&Object.prototype.toString.call(maybeBlob)==="[object Blob]"};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice!==undefined||File.prototype.webkitSlice!==undefined||File.prototype.mozSlice!==undefined)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}})};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{defaultResponseError:"Upload failure reason unknown",sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"misc_data",paramNames:{name:"qqblobname"}},paste:{targetElement:null,defaultName:"pasted_image"}};qq.extend(this._options,o,true);this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._netUploadedOrQueued=0;this._netUploaded=0;this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}if(this._options.paste.targetElement){this._pasteHandler=this._createPasteHandler()}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},getNetUploads:function(){return this._netUploaded},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._netUploadedOrQueued++;this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset();this._netUploadedOrQueued=0;this._netUploaded=0;if(this._pasteHandler){this._pasteHandler.reset()}},addFiles:function(filesDataOrInputs,params,endpoint){var self=this,verifiedFilesOrInputs=[],index,fileOrInput;if(filesDataOrInputs){if(!window.FileList||!(filesDataOrInputs instanceof FileList)){filesDataOrInputs=[].concat(filesDataOrInputs)}for(index=0;index=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||qq.supportedFeatures.deleteFileCors)},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)!==false){this._deleteHandler.sendDelete(id,this.getUuid(id))}}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(fileId){},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);if(isError){this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status,xhr)}else{this._netUploadedOrQueued--;this._netUploaded--;this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){},_onInputChange:function(input){if(qq.supportedFeatures.ajaxUploading){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0&&this._netUploadedOrQueued+1>itemLimit){this._itemError("retryFailTooManyItems","");return false}this.log("Retrying upload for '"+fileName+"' (id: "+id+")...");this._filesInProgress.push(id);return true}else{this.log("'"+id+"' is not a valid file ID","error");return false}},_maybeParseAndSendUploadError:function(id,name,response,xhr){if(!response.success){if(xhr&&xhr.status!==200&&!response.error){this._options.callbacks.onError(id,name,"XHR returned response code "+xhr.status,xhr)}else{var errorReason=response.error?response.error:this._options.text.defaultResponseError;this._options.callbacks.onError(id,name,errorReason,xhr)}}},_uploadFileOrBlobDataList:function(fileOrBlobDataList,params,endpoint){var index,validationDescriptors=this._getValidationDescriptors(fileOrBlobDataList),batchValid=this._isBatchValid(validationDescriptors);if(batchValid){if(fileOrBlobDataList.length>0){for(index=0;indexthis._options.validation.sizeLimit){this._itemError("sizeError",name);return false}else if(size&&size99);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];
+self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size!==undefined){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,droppedFiles=[],disposeSupport=new qq.DisposeSupport;options={dropZoneElements:[],hideDropZonesBeforeEnter:false,allowMultipleItems:true,classes:{dropActive:null},callbacks:new qq.DragAndDrop.callbacks};qq.extend(options,o,true);setupDragDrop();function uploadDroppedFiles(files){options.callbacks.dropLog("Grabbed "+files.length+" dropped files.");dz.dropDisabled(false);options.callbacks.processingDroppedFilesComplete(files)}function traverseFileTree(entry){var dirReader,i,parseEntryPromise=new qq.Promise;if(entry.isFile){entry.file(function(file){droppedFiles.push(file);parseEntryPromise.success()},function(fileError){options.callbacks.dropLog("Problem parsing '"+entry.fullPath+"'. FileError code "+fileError.code+".","error");parseEntryPromise.failure()})}else if(entry.isDirectory){dirReader=entry.createReader();dirReader.readEntries(function(entries){var entriesLeft=entries.length;for(i=0;i1&&!options.allowMultipleItems){options.callbacks.processingDroppedFilesComplete([]);options.callbacks.dropError("tooManyFilesError","");dz.dropDisabled(false);handleDataTransferPromise.failure()}else{droppedFiles=[];if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file",unsupportedBrowser:"Unrecoverable error - this browser does not permit file uploading of any kind."},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false},paste:{promptForName:false,namePromptMessage:"Please name this image"},showMessage:function(message){setTimeout(function(){window.alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=window.confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)},showPrompt:function(message,defaultValue){var promise=new qq.Promise,retVal=window.prompt(message,defaultValue);if(retVal!=null&&qq.trimStr(retVal).length>0){promise.success(retVal)}else{promise.failure("Undefined or invalid user-supplied value.")}return promise}},true);qq.extend(this._options,o,true);if(!qq.supportedFeatures.uploading||this._options.cors.expected&&!qq.supportedFeatures.uploadCors){this._options.element.innerHTML=""+this._options.messages.unsupportedBrowser+"
"}else{this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop();if(this._options.paste.targetElement&&this._options.paste.promptForName){this._setupPastePrompt()}}};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop()},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dropZoneElements=this._options.dragAndDrop.extraDropzones,preventSelectFiles;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){dropZoneElements.push(this._find(this._options.element,"drop"))}return new qq.DragAndDrop({dropZoneElements:dropZoneElements,hideDropZonesBeforeEnter:this._options.dragAndDrop.hideDropzones,allowMultipleItems:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{processingDroppedFiles:function(){var input=self._button.getInput();qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)},processingDroppedFilesComplete:function(files){var input=self._button.getInput();qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles);if(files){self.addFiles(files)}},dropError:function(code,errorData){self._itemError(code,errorData)},dropLog:function(message,level){self.log(message,level)}}})},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){var item=this.getItemByFileId(id);if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}else{qq(item).addClass(this._classes.retryable);return false}},_onSubmitDelete:function(id){if(this._isDeletePossible()){if(this._options.callbacks.onSubmitDelete(id)!==false){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}}}else{this.log("Delete request ignored for file ID "+id+", delete feature is disabled.","warn");return false}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}this._listElement.appendChild(item);if(this._options.display.fileSizeOnSubmit&&qq.supportedFeatures.ajaxUploading){this._displayFileSize(id)}},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=item.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_itemError:function(code,name){var message=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(message)},_batchError:function(message){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments);this._options.showMessage(message)},_setupPastePrompt:function(){var self=this;this._options.callbacks.onPasteReceived=function(){var message=self._options.paste.namePromptMessage,defaultVal=self._options.paste.defaultName;return self._options.showPrompt(message,defaultVal)}}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,dequeue,handlerImpl;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){}};qq.extend(options,o);log=options.log;dequeue=function(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){log("Cancelling "+id);options.paramsStore.remove(id);handlerImpl.cancel(id);dequeue(id)},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},getQueue:function(){return queue},reset:function(){log("Resetting upload handler");queue=[];handlerImpl.reset()},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}}};qq.UploadHandlerForm=function(o,uploadCompleteCallback,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var id=iframe.id;onloadCallbacks[uuids[id]]=callback;detachLoadEvents[id]=qq(iframe).attach("load",function(){if(inputs[id]){log("Received iframe load event for CORS upload request (file id "+id+")");postMessageCallbackTimers[id]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for file id "+id;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(id,function(message){log("Received the following window message: '"+message+"'");var response=qq.parseJson(message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];detachLoadEvent(id);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(id);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)
+}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHTML=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHTML);if(innerHTML&&innerHTML.match(/^ ');iframe.setAttribute("id",id);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){if(api.isValid(id)){return inputs[id].value.replace(/.*(\/|\\)/,"")}else{log(id+" is not a valid item ID.","error")}},isValid:function(id){return inputs[id]!==undefined},reset:function(){inputs=[];uuids=[];detachLoadEvents={}},getUuid:function(id){return uuids[id]},cancel:function(id){options.onCancel(id,this.getName(id));delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(id);if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,this.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove();return id}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,logCallback){"use strict";var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.supportedFeatures.chunking,resumeEnabled=options.resume.enabled&&chunkFiles&&qq.supportedFeatures.resume,resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);delete fileState[id].xhr;uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(xhr){var response;try{response=qq.parseJson(xhr.responseText)}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function handleFileChunkingUpload(id,retry){var name=api.getName(id),firstChunkIndex=0,persistedChunkInfoForResume,firstChunkDataForResume,currentChunkIndex;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part);if(options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume))!==false){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].uuid=persistedChunkInfoForResume.uuid;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex)}}}for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}}uploadNextChunk(id)}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}api={add:function(fileOrBlobData){var id;if(fileOrBlobData instanceof File){id=fileState.push({file:fileOrBlobData})-1}else if(qq.isBlob(fileOrBlobData.blob)){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}fileState[id].uuid=qq.getUniqueId();return id},getName:function(id){if(api.isValid(id)){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}}else{log(id+" is not a valid item ID.","error")}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},getLoaded:function(id){return fileState[id].loaded||0},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}},cancel:function(id){var xhr=fileState[id].xhr;options.onCancel(id,this.getName(id));if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.5.0/iframe.xss.response.js b/ajax/libs/file-uploader/3.5.0/iframe.xss.response.js
new file mode 100644
index 000000000..e11fca1e5
--- /dev/null
+++ b/ajax/libs/file-uploader/3.5.0/iframe.xss.response.js
@@ -0,0 +1,6 @@
+(function() {
+ var match = /(\{.+\}).+/.exec(document.body.innerHTML);
+ if (match) {
+ parent.postMessage(match[1], '*');
+ }
+}());
diff --git a/ajax/libs/file-uploader/3.5.0/iframe.xss.response.min.js b/ajax/libs/file-uploader/3.5.0/iframe.xss.response.min.js
new file mode 100644
index 000000000..345cf3603
--- /dev/null
+++ b/ajax/libs/file-uploader/3.5.0/iframe.xss.response.min.js
@@ -0,0 +1 @@
+!function(){var match=/(\{.+\}).+/.exec(document.body.innerHTML);if(match){parent.postMessage(match[1],"*")}}();
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.5.0/loading.gif b/ajax/libs/file-uploader/3.5.0/loading.gif
new file mode 100644
index 000000000..6fba77609
Binary files /dev/null and b/ajax/libs/file-uploader/3.5.0/loading.gif differ
diff --git a/ajax/libs/file-uploader/3.5.0/processing.gif b/ajax/libs/file-uploader/3.5.0/processing.gif
new file mode 100644
index 000000000..7c99504e1
Binary files /dev/null and b/ajax/libs/file-uploader/3.5.0/processing.gif differ
diff --git a/ajax/libs/file-uploader/3.6.0/fineuploader-jquery.js b/ajax/libs/file-uploader/3.6.0/fineuploader-jquery.js
new file mode 100644
index 000000000..fdb64f6c9
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.0/fineuploader-jquery.js
@@ -0,0 +1,5032 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: -unstable-
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity != null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isArray = function(variable) {
+ "use strict";
+ return Object.prototype.toString.call(variable) === "[object Array]";
+}
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+ if (window.File && maybeFileOrInput instanceof File) {
+ return true;
+ }
+
+ return qq.isInput(maybeFileOrInput);
+};
+
+qq.isInput = function(maybeInput) {
+ if (window.HTMLInputElement) {
+ if (maybeInput instanceof HTMLInputElement) {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ else if (maybeInput.tagName) {
+ if (maybeInput.tagName.toLowerCase() === 'input') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && Object.prototype.toString.call(maybeBlob) === '[object Blob]';
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice !== undefined || File.prototype.webkitSlice !== undefined || File.prototype.mozSlice !== undefined);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+
+ return first;
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+qq.ios = function() {
+ "use strict";
+ return navigator.userAgent.indexOf("iPad") !== -1
+ || navigator.userAgent.indexOf("iPod") !== -1
+ || navigator.userAgent.indexOf("iPhone") !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(objOrArray, callback) {
+ "use strict";
+ var keyOrIndex, retVal;
+ if (objOrArray) {
+ if (qq.isArray(objOrArray)) {
+ for (keyOrIndex = 0; keyOrIndex < objOrArray.length; keyOrIndex++) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ else {
+ for (keyOrIndex in objOrArray) {
+ if (Object.prototype.hasOwnProperty.call(objOrArray, keyOrIndex)) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+ }
+};
+
+//include any args that should be passed to the new function after the context arg
+qq.bind = function(oldFunc, context) {
+ if (qq.isFunction(oldFunc)) {
+ var args = Array.prototype.slice.call(arguments, 2);
+
+ return function() {
+ if (arguments.length) {
+ args = args.concat(Array.prototype.slice.call(arguments))
+ }
+ return oldFunc.apply(context, args);
+ };
+ }
+
+ throw new Error("first parameter must be a function!");
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ cookie;
+
+ qq.each(ca, function(idx, part) {
+ var cookiePart = part;
+ while (cookiePart.charAt(0)==' ') {
+ cookiePart = cookiePart.substring(1, cookiePart.length);
+ }
+
+ if (cookiePart.indexOf(nameEQ) === 0) {
+ cookie = cookiePart.substring(nameEQ.length, cookiePart.length);
+ return false;
+ }
+ });
+
+ return cookie;
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+qq.version="-unstable-";qq.supportedFeatures = (function() {
+ var supportsUploading,
+ supportsAjaxFileUploading,
+ supportsFolderDrop,
+ supportsChunking,
+ supportsResume,
+ supportsUploadViaPaste,
+ supportsUploadCors,
+ supportsDeleteFileCors;
+
+
+ function testSupportsFileInputElement() {
+ var supported = true,
+ tempInput;
+
+ try {
+ tempInput = document.createElement('input');
+ tempInput.type = 'file';
+ qq(tempInput).hide();
+
+ if(tempInput.disabled) {
+ supported = false;
+ }
+ }
+ catch(ex) {
+ supported = false;
+ }
+
+ return supported;
+ }
+
+ //only way to test for Filesystem API support since webkit does not expose the DataTransfer interface
+ function isChrome21OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[2][1-9]|Chrome\/[3-9][0-9]/) !== undefined;
+ }
+
+ //only way to test for complete Clipboard API support at this time
+ function isChrome14OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/) !== undefined;
+ }
+
+
+ supportsUploading = testSupportsFileInputElement();
+
+ supportsAjaxFileUploading = supportsUploading && qq.isXhrUploadSupported();
+
+ supportsFolderDrop = supportsAjaxFileUploading && isChrome21OrHigher();
+
+ supportsChunking = supportsAjaxFileUploading && qq.isFileChunkingSupported();
+
+ supportsResume = supportsAjaxFileUploading && supportsChunking && qq.areCookiesEnabled();
+
+ supportsUploadViaPaste = supportsAjaxFileUploading && isChrome14OrHigher();
+
+ supportsUploadCors = supportsUploading && (window.postMessage !== undefined || supportsAjaxFileUploading);
+
+ supportsDeleteFileCors = supportsAjaxFileUploading;
+
+
+ return {
+ uploading: supportsUploading,
+ ajaxUploading: supportsAjaxFileUploading,
+ fileDrop: supportsAjaxFileUploading, //NOTE: will also return true for touch-only devices. It's not currently possible to accurately test for touch-only devices
+ folderDrop: supportsFolderDrop,
+ chunking: supportsChunking,
+ resume: supportsResume,
+ uploadCustomHeaders: supportsAjaxFileUploading,
+ uploadNonMultipart: supportsAjaxFileUploading,
+ itemSizeValidation: supportsAjaxFileUploading,
+ uploadViaPaste: supportsUploadViaPaste,
+ progressBar: supportsAjaxFileUploading,
+ uploadCors: supportsUploadCors,
+ deleteFileCors: supportsDeleteFileCors,
+ canDetermineSize: supportsAjaxFileUploading
+ }
+
+}());
+/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallbacks = [],
+ failureCallbacks = [],
+ doneCallbacks = [],
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ if (onSuccess) {
+ successCallbacks.push(onSuccess);
+ }
+ if (onFailure) {
+ failureCallbacks.push(onFailure);
+ }
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+
+ return this;
+ },
+
+ done: function(callback) {
+ if (state === 0) {
+ doneCallbacks.push(callback);
+ }
+ else {
+ callback();
+ }
+
+ return this;
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallbacks.length) {
+ qq.each(successCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallbacks.length) {
+ qq.each(failureCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ }
+ };
+};
+
+qq.isPromise = function(maybePromise) {
+ return maybePromise && maybePromise.then && maybePromise.done;
+};/*globals qq*/
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ disposeSupport = new qq.DisposeSupport(),
+ options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input) {},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+
+ qq.extend(options, o);
+
+ // make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};qq.UploadData = function(uploaderProxy) {
+ var data = [],
+ byId = {},
+ byUuid = {},
+ byStatus = {},
+ api;
+
+ function getDataByIds(ids) {
+ if (qq.isArray(ids)) {
+ var entries = [];
+
+ qq.each(ids, function(idx, id) {
+ entries.push(data[byId[id]]);
+ });
+
+ return entries;
+ }
+
+ return data[byId[ids]];
+ }
+
+ function getDataByUuids(uuids) {
+ if (qq.isArray(uuids)) {
+ var entries = [];
+
+ qq.each(uuids, function(idx, uuid) {
+ entries.push(data[byUuid[uuid]]);
+ });
+
+ return entries;
+ }
+
+ return data[byUuid[uuids]];
+ }
+
+ function getDataByStatus(status) {
+ var statusResults = [],
+ statuses = [].concat(status);
+
+ qq.each(statuses, function(index, statusEnum) {
+ var statusResultIndexes = byStatus[statusEnum];
+
+ if (statusResultIndexes !== undefined) {
+ qq.each(statusResultIndexes, function(i, dataIndex) {
+ statusResults.push(data[dataIndex]);
+ });
+ }
+ });
+
+ return statusResults;
+ }
+
+ api = {
+ added: function(id) {
+ var uuid = uploaderProxy.getUuid(id),
+ name = uploaderProxy.getName(id),
+ size = uploaderProxy.getSize(id),
+ status = qq.status.SUBMITTING;
+
+ var index = data.push({
+ id: id,
+ name: name,
+ uuid: uuid,
+ size: size,
+ status: status
+ }) - 1;
+
+ byId[id] = index;
+
+ byUuid[uuid] = index;
+
+ if (byStatus[status] === undefined) {
+ byStatus[status] = [];
+ }
+ byStatus[status].push(index);
+
+ uploaderProxy.onStatusChange(id, undefined, status);
+ },
+
+ retrieve: function(optionalFilter) {
+ if (qq.isObject(optionalFilter) && data.length) {
+ if (optionalFilter.id !== undefined) {
+ return getDataByIds(optionalFilter.id);
+ }
+
+ else if (optionalFilter.uuid !== undefined) {
+ return getDataByUuids(optionalFilter.uuid);
+ }
+
+ else if (optionalFilter.status) {
+ return getDataByStatus(optionalFilter.status);
+ }
+ }
+ else {
+ return qq.extend([], data, true);
+ }
+ },
+
+ reset: function() {
+ data = [];
+ byId = {};
+ byUuid = {};
+ byStatus = {};
+ },
+
+ setStatus: function(id, newStatus) {
+ var dataIndex = byId[id],
+ oldStatus = data[dataIndex].status,
+ byStatusOldStatusIndex = qq.indexOf(byStatus[oldStatus], dataIndex);
+
+ byStatus[oldStatus].splice(byStatusOldStatusIndex, 1);
+
+ data[dataIndex].status = newStatus;
+
+ if (byStatus[newStatus] === undefined) {
+ byStatus[newStatus] = [];
+ }
+ byStatus[newStatus].push(dataIndex);
+
+ uploaderProxy.onStatusChange(id, oldStatus, newStatus);
+ },
+
+ uuidChanged: function(id, newUuid) {
+ var dataIndex = byId[id],
+ oldUuid = data[dataIndex].uuid;
+
+ data[dataIndex].uuid = newUuid;
+ byUuid[newUuid] = dataIndex;
+ delete byUuid[oldUuid];
+ }
+ };
+
+ return api;
+};
+
+qq.status = {
+ SUBMITTING: "submitting",
+ SUBMITTED: "submitted",
+ REJECTED: "rejected",
+ QUEUED: "queued",
+ CANCELED: "canceled",
+ UPLOADING: "uploading",
+ UPLOAD_RETRYING: "retrying upload",
+ UPLOAD_SUCCESSFUL: "upload successful",
+ UPLOAD_FAILED: "upload failed",
+ DELETE_FAILED: "delete failed",
+ DELETING: "deleting",
+ DELETED: "deleted"
+};qq.FineUploaderBasic = function(o) {
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true,
+ acceptFiles: null
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON, maybeXhr){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){},
+ onPasteReceived: function(blob) {},
+ onStatusChange: function(id, oldStatus, newStatus) {}
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ defaultResponseError: "Upload failure reason unknown",
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'misc_data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ },
+ camera: {
+ ios: false
+ }
+ };
+
+ qq.extend(this._options, o, true);
+
+ this._handleCameraAccess();
+
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData = this._createUploadDataTracker();
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader ' + qq.version + '] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader ' + qq.version + '] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function() {
+ return this._filesInProgress.length;
+ },
+ getNetUploads: function() {
+ return this._netUploaded;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netUploadedOrQueued++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData.reset();
+
+ if (this._pasteHandler) {
+ this._pasteHandler.reset();
+ }
+ },
+ addFiles: function(filesOrInputs, params, endpoint) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ fileOrInputIndex, fileOrInput, fileIndex;
+
+ if (filesOrInputs) {
+ if (!window.FileList || !(filesOrInputs instanceof FileList)) {
+ filesOrInputs = [].concat(filesOrInputs);
+ }
+
+ for (fileOrInputIndex = 0; fileOrInputIndex < filesOrInputs.length; fileOrInputIndex+=1) {
+ fileOrInput = filesOrInputs[fileOrInputIndex];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ if (qq.isInput(fileOrInput) && qq.supportedFeatures.ajaxUploading) {
+ for (fileIndex = 0; fileIndex < fileOrInput.files.length; fileIndex++) {
+ verifiedFilesOrInputs.push(fileOrInput.files[fileIndex]);
+ }
+ }
+ else {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Received ' + verifiedFilesOrInputs.length + ' files or inputs.');
+ this._prepareItemsForUpload(verifiedFilesOrInputs, params, endpoint);
+ }
+ },
+ addBlobs: function(blobDataOrArray, params, endpoint) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._prepareItemsForUpload(verifiedBlobDataList, params, endpoint);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ doesExist: function(fileOrBlobId) {
+ return this._handler.isValid(fileOrBlobId);
+ },
+ getUploads: function(optionalFilter) {
+ return this._uploadData.retrieve(optionalFilter);
+ },
+ _handleCheckedCallback: function(details) {
+ var self = this,
+ callbackRetVal = details.callback();
+
+ if (qq.isPromise(callbackRetVal)) {
+ this.log(details.name + " - waiting for " + details.name + " promise to be fulfilled for " + details.identifier);
+ return callbackRetVal.then(
+ function(successParam) {
+ self.log(details.name + " promise success for " + details.identifier);
+ details.onSuccess(successParam);
+ },
+ function() {
+ if (details.onFailure) {
+ self.log(details.name + " promise failure for " + details.identifier);
+ details.onFailure();
+ }
+ else {
+ self.log(details.name + " promise failure for " + details.identifier);
+ }
+ });
+ }
+
+ if (callbackRetVal !== false) {
+ details.onSuccess(callbackRetVal);
+ }
+ else {
+ if (details.onFailure) {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Invoking failure callback.")
+ details.onFailure();
+ }
+ else {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Will not proceed.")
+ }
+ }
+
+ return callbackRetVal;
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.supportedFeatures.ajaxUploading,
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result, xhr);
+ },
+ onCancel: function(id, name) {
+ return self._handleCheckedCallback({
+ name: "onCancel",
+ callback: qq.bind(self._options.callbacks.onCancel, self, id, name),
+ onSuccess: qq.bind(self._onCancel, self, id, name),
+ identifier: id
+ });
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ onUuidChanged: function(id, newUuid) {
+ self._uploadData.uuidChanged(id, newUuid);
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ self._handleCheckedCallback({
+ name: "onPasteReceived",
+ callback: qq.bind(self._options.callbacks.onPasteReceived, self, blob),
+ onSuccess: qq.bind(self._handlePasteSuccess, self, blob),
+ identifier: "pasted image"
+ });
+ }
+ }
+ });
+ },
+ _createUploadDataTracker: function() {
+ var self = this;
+
+ return new qq.UploadData({
+ getName: function(id) {
+ return self.getName(id);
+ },
+ getUuid: function(id) {
+ return self.getUuid(id);
+ },
+ getSize: function(id) {
+ return self.getSize(id);
+ },
+ onStatusChange: function(id, oldStatus, newStatus) {
+ self._options.callbacks.onStatusChange(id, oldStatus, newStatus);
+ }
+ });
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total) {
+ //nothing to do yet in core uploader
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_FAILED);
+ this._netUploadedOrQueued--;
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_SUCCESSFUL);
+ this._netUploaded++;
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.CANCELED);
+
+ this._netUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected || qq.supportedFeatures.deleteFileCors));
+ },
+ _onSubmitDelete: function(id, onSuccessCallback) {
+ if (this._isDeletePossible()) {
+ return this._handleCheckedCallback({
+ name: "onSubmitDelete",
+ callback: qq.bind(this._options.callbacks.onSubmitDelete, this, id),
+ onSuccess: onSuccessCallback || qq.bind(this._deleteHandler.sendDelete, this, id, this.getUuid(id)),
+ identifier: id
+ });
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(id) {
+ this._uploadData.setStatus(id, qq.status.DELETING);
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this._uploadData.setStatus(id, qq.status.DELETE_FAILED);
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.DELETED);
+ this._netUploadedOrQueued--;
+ this._netUploaded--;
+ this._handler.expunge(id);
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.UPLOADING);
+ },
+ _onInputChange: function(input){
+ if (qq.supportedFeatures.ajaxUploading) {
+ this.addFiles(input.files);
+ }
+ else {
+ this.addFiles(input);
+ }
+
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems", "");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : this._options.text.defaultResponseError;
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ var validationDescriptors = this._getValidationDescriptors(items);
+
+ this._handleCheckedCallback({
+ name: "onValidateBatch",
+ callback: qq.bind(this._options.callbacks.onValidateBatch, this, validationDescriptors),
+ onSuccess: qq.bind(this._onValidateBatchCallbackSuccess, this, validationDescriptors, items, params, endpoint),
+ identifier: "batch validation"
+ });
+ },
+ _upload: function(blobOrFileContainer, params, endpoint) {
+ var id = this._handler.add(blobOrFileContainer),
+ name = this._handler.getName(id);
+
+ this._uploadData.added(id);
+
+ if (params) {
+ this.setParams(params, id);
+ }
+
+ if (endpoint) {
+ this.setEndpoint(endpoint, id);
+ }
+
+ this._handleCheckedCallback({
+ name: "onSubmit",
+ callback: qq.bind(this._options.callbacks.onSubmit, this, id, name),
+ onSuccess: qq.bind(this._onSubmitCallbackSuccess, this, id, name),
+ onFailure: qq.bind(this._fileOrBlobRejected, this, id, name),
+ identifier: id
+ });
+ },
+ _onSubmitCallbackSuccess: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.SUBMITTED);
+
+ this._onSubmit(id, name);
+ this._options.callbacks.onSubmitted(id, name);
+
+ if (this._options.autoUpload) {
+ if (!this._handler.upload(id)) {
+ this._uploadData.setStatus(id, qq.status.QUEUED);
+ }
+ }
+ else {
+ this._storeForLater(id);
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _onValidateBatchCallbackSuccess: function(validationDescriptors, items, params, endpoint) {
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netUploadedOrQueued + validationDescriptors.length;
+
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ if (items.length > 0) {
+ this._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(this._options.callbacks.onValidate, this, items[0]),
+ onSuccess: qq.bind(this._onValidateCallbackSuccess, this, items, 0, params, endpoint),
+ onFailure: qq.bind(this._onValidateCallbackFailure, this, items, 0, params, endpoint),
+ identifier: "Item '" + items[0].name + "', size: " + items[0].size
+ });
+ }
+ else {
+ this._itemError("noFilesError", "");
+ }
+ }
+ else {
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ },
+ _onValidateCallbackSuccess: function(items, index, params, endpoint) {
+ var nextIndex = index+1,
+ validationDescriptor = this._getValidationDescriptor(items[index]),
+ validItem = false;
+
+ if (this._validateFileOrBlobData(items[index], validationDescriptor)) {
+ validItem = true;
+ this._upload(items[index], params, endpoint);
+ }
+
+ this._maybeProcessNextItemAfterOnValidateCallback(validItem, items, nextIndex, params, endpoint);
+ },
+ _onValidateCallbackFailure: function(items, index, params, endpoint) {
+ var nextIndex = index+ 1;
+
+ this._fileOrBlobRejected(undefined, items[0].name);
+
+ this._maybeProcessNextItemAfterOnValidateCallback(false, items, nextIndex, params, endpoint);
+ },
+ _maybeProcessNextItemAfterOnValidateCallback: function(validItem, items, index, params, endpoint) {
+ var self = this;
+
+ if (items.length > index) {
+ if (validItem || !this._options.validation.stopOnFirstInvalidFile) {
+ //use setTimeout to prevent a stack overflow with a large number of files in the batch & non-promissory callbacks
+ setTimeout(function() {
+ var validationDescriptor = self._getValidationDescriptor(items[index]);
+
+ self._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(self._options.callbacks.onValidate, self, items[index]),
+ onSuccess: qq.bind(self._onValidateCallbackSuccess, self, items, index, params, endpoint),
+ onFailure: qq.bind(self._onValidateCallbackFailure, self, items, index, params, endpoint),
+ identifier: "Item '" + validationDescriptor.name + "', size: " + validationDescriptor.size
+ });
+ }, 0);
+ }
+ }
+ },
+ _validateFileOrBlobData: function(item, validationDescriptor) {
+ var name = validationDescriptor.name,
+ size = validationDescriptor.size,
+ valid = true;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ valid = false;
+ }
+
+ if (qq.isFileOrInput(item) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ valid = false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ valid = false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ valid = false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ valid = false;
+ }
+
+ if (!valid) {
+ this._fileOrBlobRejected(undefined, name);
+ }
+
+ return valid;
+ },
+ _fileOrBlobRejected: function(id, name) {
+ if (id !== undefined) {
+ this._uploadData.setStatus(id, qq.status.REJECTED);
+ }
+ },
+ _itemError: function(code, nameOrNames) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ names = [].concat(nameOrNames),
+ name = names[0],
+ extensionsForMessage, placeholderMatch;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ placeholderMatch = message.match(/(\{\w+\})/g);
+ if (placeholderMatch !== null) {
+ qq.each(placeholderMatch, function(idx, placeholder) {
+ r(placeholder, names[idx]);
+ });
+ }
+
+ this._options.callbacks.onError(null, name, message, undefined);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message, undefined);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1000;
+ i++;
+ } while (bytes > 999);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size !== undefined) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ },
+ _handleCameraAccess: function() {
+ if (this._options.camera.ios && qq.ios()) {
+ this._options.multiple = false;
+
+ if (this._options.validation.acceptFiles === null) {
+ this._options.validation.acceptFiles = "image/*;capture=camera";
+ }
+ else {
+ this._options.validation.acceptFiles += ",image/*;capture=camera";
+ }
+ }
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz,
+ droppedFiles = [],
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropZoneElements: [],
+ hideDropZonesBeforeEnter: false,
+ allowMultipleItems: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: new qq.DragAndDrop.callbacks()
+ };
+
+ qq.extend(options, o, true);
+
+ setupDragDrop();
+
+ function uploadDroppedFiles(files) {
+ options.callbacks.dropLog('Grabbed ' + files.length + " dropped files.");
+ dz.dropDisabled(false);
+ options.callbacks.processingDroppedFilesComplete(files);
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i,
+ parseEntryPromise = new qq.Promise();
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ droppedFiles.push(file);
+ parseEntryPromise.success();
+ },
+ function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+ else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ var entriesLeft = entries.length;
+
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]).done(function() {
+ entriesLeft-=1;
+
+ if (entriesLeft === 0) {
+ parseEntryPromise.success();
+ }
+ });
+ }
+
+ if (!entries.length) {
+ parseEntryPromise.success();
+ }
+ }, function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+
+ return parseEntryPromise;
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry,
+ pendingFolderPromises = [],
+ handleDataTransferPromise = new qq.Promise();
+
+ options.callbacks.processingDroppedFiles();
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.allowMultipleItems) {
+ options.callbacks.processingDroppedFilesComplete([]);
+ options.callbacks.dropError('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ handleDataTransferPromise.failure();
+ }
+ else {
+ droppedFiles = [];
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ }
+
+ else {
+ pendingFolderPromises.push(traverseFileTree(entry).done(function() {
+ pendingFolderPromises.pop();
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }));
+ }
+ }
+ }
+ }
+ else {
+ droppedFiles = dataTransfer.files;
+ }
+
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }
+
+ return handleDataTransferPromise;
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer).done(function() {
+ uploadDroppedFiles(droppedFiles);
+ });
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ var dropZones = options.dropZoneElements;
+
+ qq.each(dropZones, function(idx, dropZone) {
+ setupDropzone(dropZone);
+ })
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (dropZones.length && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).css({display: 'block'});
+ });
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropZonesBeforeEnter && qq.FineUploader.prototype._leaving_document_out(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setupExtraDropzone: function(element) {
+ options.dropZoneElements.push(element);
+ setupDropzone(element);
+ },
+
+ removeDropzone: function(element) {
+ var i,
+ dzs = options.dropZoneElements;
+
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+qq.DragAndDrop.callbacks = function() {
+ return {
+ processingDroppedFiles: function() {},
+ processingDroppedFilesComplete: function(files) {},
+ dropError: function(code, errorSpecifics) {
+ qq.log("Drag & drop error code '" + code + " with these specifics: '" + errorSpecifics + "'", "error");
+ },
+ dropLog: function(message, level) {
+ qq.log(message, level);
+ }
+ }
+}
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file",
+ unsupportedBrowser: "Unrecoverable error - this browser does not permit file uploading of any kind."
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false,
+ prependFiles: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+
+ if (!qq.supportedFeatures.uploading || (this._options.cors.expected && !qq.supportedFeatures.uploadCors)) {
+ this._options.element.innerHTML = "" + this._options.messages.unsupportedBrowser + "
"
+ }
+ else {
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dropZoneElements = this._options.dragAndDrop.extraDropzones,
+ preventSelectFiles;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ dropZoneElements.push(this._find(this._options.element, 'drop'));
+ }
+
+ return new qq.DragAndDrop({
+ dropZoneElements: dropZoneElements,
+ hideDropZonesBeforeEnter: this._options.dragAndDrop.hideDropzones,
+ allowMultipleItems: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ processingDroppedFiles: function() {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ },
+ processingDroppedFilesComplete: function(files) {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ dropError: function(code, errorData) {
+ self._itemError(code, errorData);
+ },
+ dropLog: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type) {
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name) {
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ var onSuccessCallback = qq.bind(this._onSubmitDeleteSuccess, this, id);
+
+ qq.FineUploaderBasic.prototype._onSubmitDelete.call(this, id, onSuccessCallback);
+ },
+ _onSubmitDeleteSuccess: function(id) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ if (this._options.display.prependFiles) {
+ this._prependItem(item);
+ }
+ else {
+ this._listElement.appendChild(item);
+ }
+ this._filesInBatchAddedToUi += 1;
+
+ if (this._options.display.fileSizeOnSubmit && qq.supportedFeatures.ajaxUploading) {
+ this._displayFileSize(id);
+ }
+ },
+ _prependItem: function(item) {
+ var parentEl = this._listElement,
+ beforeEl = parentEl.firstChild;
+
+ if (this._totalFilesInBatch > 1 && this._filesInBatchAddedToUi > 0) {
+ beforeEl = qq(parentEl).children()[this._filesInBatchAddedToUi - 1].nextSibling;
+
+ }
+
+ parentEl.insertBefore(item, beforeEl);
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = item.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ },
+ _fileOrBlobRejected: function(id, name) {
+ this._totalFilesInBatch -= 1;
+ qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this, arguments);
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ this._totalFilesInBatch = items.length;
+ this._filesInBatchAddedToUi = 0;
+ qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this, arguments);
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, handlerImpl, api;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){},
+ onUuidChanged: function(id, newUuid){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.supportedFeatures.ajaxUploading) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, options.onUuidChanged, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, options.onUuidChanged, log);
+ }
+
+ function cancelSuccess(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ dequeue(id);
+ }
+
+
+ api = {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ handlerImpl.upload(id);
+ return true;
+ }
+
+ return false;
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ var cancelRetVal = handlerImpl.cancel(id);
+
+ if (qq.isPromise(cancelRetVal)) {
+ cancelRetVal.then(function() {
+ cancelSuccess(id);
+ });
+ }
+ else if (cancelRetVal !== false) {
+ cancelSuccess(id);
+ }
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ api.cancelAll();
+ queue = [];
+ handlerImpl.reset();
+ },
+ expunge: function(id) {
+ return handlerImpl.expunge(id);
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, onUuidChange, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ formHandlerInstanceId = qq.getUniqueId(),
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var iframeName = iframe.id,
+ fileId = getFileIdForIframeName(iframeName);
+
+ onloadCallbacks[uuids[fileId]] = callback;
+
+ detachLoadEvents[fileId] = qq(iframe).attach('load', function() {
+ if (inputs[fileId]) {
+ log("Received iframe load event for CORS upload request (iframe name " + iframeName + ")");
+
+ postMessageCallbackTimers[iframeName] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for iframe name " + iframeName;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(iframeName, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = parseResponse(getFileIdForIframeName(iframeName), message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ log("Handling response for iframe name " + iframeName);
+ clearTimeout(postMessageCallbackTimers[iframeName]);
+ delete postMessageCallbackTimers[iframeName];
+
+ detachLoadEvent(iframeName);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(iframeName);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(id, iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHtml = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHtml);
+ //plain text response may be wrapped in tag
+ if (innerHtml && innerHtml.match(/^ ');
+
+ iframe.setAttribute('id', iframeName);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+ function expungeFile(id) {
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(getIframeName(id));
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ }
+
+ function getFileIdForIframeName(iframeName) {
+ return iframeName.split("_")[0];
+ }
+
+ function getIframeName(fileId) {
+ return fileId + "_" + formHandlerInstanceId;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ formHandlerInstanceId = qq.getUniqueId();
+ },
+ expunge: function(id) {
+ return expungeFile(id);
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, api.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeFile(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeFile(id);
+ return true;
+ }
+
+ return false;
+ },
+
+ upload: function(id) {
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, api.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(id, iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, onUuidChange, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.supportedFeatures.chunking,
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.supportedFeatures.resume,
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+ options.onComplete(id, name, response, xhr);
+
+ if (fileState[id]) {
+ delete fileState[id].xhr;
+ }
+
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(id, xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+
+ if (response.newUuid !== undefined) {
+ log("Server requested UUID change from '" + fileState[id].uuid + "' to '" + response.newUuid + "'");
+ fileState[id].uuid = response.newUuid;
+ onUuidChanged(id, response.newUuid);
+ }
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(id, xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex) {
+ var currentChunkIndex;
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+
+ function handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex) {
+ var name = api.getName(id),
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part),
+ onResumeRetVal;
+
+ onResumeRetVal = options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume));
+ if (qq.isPromise(onResumeRetVal)) {
+ log("Waiting for onResume promise to be fulfilled for " + id);
+ onResumeRetVal.then(
+ function() {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ },
+ function() {
+ log("onResume promise fulfilled - failure indicated. Will not resume.")
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ );
+ }
+ else if (onResumeRetVal !== false) {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ }
+ else {
+ log("onResume callback returned false. Will not resume.");
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var firstChunkIndex = 0,
+ persistedChunkInfoForResume;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex);
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ uploadNextChunk(id);
+ }
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+ function expungeItem(id) {
+ var xhr = fileState[id].xhr;
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ }
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id, persistedChunkData,
+ uuid = qq.getUniqueId();
+
+ if (fileOrBlobData instanceof File) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (qq.isBlob(fileOrBlobData.blob)) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ if (resumeEnabled) {
+ persistedChunkData = getPersistedChunkData(id);
+
+ if (persistedChunkData) {
+ uuid = persistedChunkData.uuid;
+ }
+ }
+
+ fileState[id].uuid = uuid;
+
+ return id;
+ },
+ getName: function(id){
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ expunge: function(id) {
+ return expungeItem(id);
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry) {
+ var name = this.getName(id);
+
+ if (this.isValid(id)) {
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ }
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, this.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeItem(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeItem(id);
+ return true;
+ }
+
+ return false;
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var uploader, $el, init, dataStore, pluginOption, pluginOptions, addCallbacks, transformVariables, isValidCommand,
+ delegateCommand;
+
+ pluginOptions = ['uploaderType'];
+
+ init = function (options) {
+ if (options) {
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+
+ if (pluginOption('uploaderType') === 'basic') {
+ uploader(new qq.FineUploaderBasic(xformedOpts));
+ }
+ else {
+ uploader(new qq.FineUploader(xformedOpts));
+ }
+ }
+
+ return $el;
+ };
+
+ dataStore = function(key, val) {
+ var data = $el.data('fineuploader');
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data('fineuploader', data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ //the underlying Fine Uploader instance is stored in jQuery's data stored, associated with the element
+ // tied to this instance of the plug-in
+ uploader = function(instanceToStore) {
+ return dataStore('uploader', instanceToStore);
+ };
+
+ pluginOption = function(option, optionVal) {
+ return dataStore(option, optionVal);
+ };
+
+ //implement all callbacks defined in Fine Uploader as functions that trigger appropriately names events and
+ // return the result of executing the bound handler back to Fine Uploader
+ addCallbacks = function(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {},
+ uploaderInst = new qq.FineUploaderBasic();
+
+ $.each(uploaderInst._options.callbacks, function(prop, func) {
+ var name, $callbackEl;
+
+ name = /^on(\w+)/.exec(prop)[1];
+ name = name.substring(0, 1).toLowerCase() + name.substring(1);
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var args = Array.prototype.slice.call(arguments);
+
+ return $callbackEl.triggerHandler(name, args);
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ transformVariables = function(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ if (source.uploaderType !== 'basic') {
+ xformed = { element : $el[0] };
+ }
+ else {
+ xformed = {};
+ }
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if ($.inArray(prop, pluginOptions) >= 0) {
+ pluginOption(prop, val);
+ }
+ else if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ isValidCommand = function(command) {
+ return $.type(command) === "string" &&
+ !command.match(/^_/) && //enforce private methods convention
+ uploader()[command] !== undefined;
+ };
+
+ //assuming we have already verified that this is a valid command, call the associated function in the underlying
+ // Fine Uploader instance (passing along the arguments from the caller) and return the result of the call back to the caller
+ delegateCommand = function(command) {
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
+
+ transformVariables(origArgs, xformedArgs);
+
+ return uploader()[command].apply(uploader(), xformedArgs);
+ };
+
+ $.fn.fineUploader = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (uploader() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error('Method ' + optionsOrCommand + ' does not exist on jQuery.fineUploader');
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
+/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var rootDataKey = "fineUploaderDnd",
+ $el;
+
+ function init (options) {
+ if (!options) {
+ options = {};
+ }
+
+ options.dropZoneElements = [$el];
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+ dnd(new qq.DragAndDrop(xformedOpts));
+
+ return $el;
+ };
+
+ function dataStore(key, val) {
+ var data = $el.data(rootDataKey);
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data(rootDataKey, data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ function dnd(instanceToStore) {
+ return dataStore('dndInstance', instanceToStore);
+ };
+
+ function addCallbacks(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {},
+ dndInst = new qq.FineUploaderBasic();
+
+ $.each(new qq.DragAndDrop.callbacks(), function(prop, func) {
+ var name = prop,
+ $callbackEl;
+
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var args = Array.prototype.slice.call(arguments),
+ jqueryHandlerResult = $callbackEl.triggerHandler(name, args);
+
+ return jqueryHandlerResult;
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ function transformVariables(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ xformed = {};
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ function isValidCommand(command) {
+ return $.type(command) === "string" &&
+ command === "dispose" &&
+ dnd()[command] !== undefined;
+ };
+
+ function delegateCommand(command) {
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
+ transformVariables(origArgs, xformedArgs);
+ return dnd()[command].apply(dnd(), xformedArgs);
+ };
+
+ $.fn.fineUploaderDnd = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (dnd() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error("Method " + optionsOrCommand + " does not exist in Fine Uploader's DnD module.");
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
diff --git a/ajax/libs/file-uploader/3.6.0/fineuploader-jquery.min.js b/ajax/libs/file-uploader/3.6.0/fineuploader-jquery.min.js
new file mode 100644
index 000000000..66c0a82f1
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.0/fineuploader-jquery.min.js
@@ -0,0 +1,16 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: -unstable-
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!=null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable!==null&&variable&&typeof variable==="object"&&variable.constructor===Object};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isArray=function(variable){"use strict";return Object.prototype.toString.call(variable)==="[object Array]"};qq.isString=function(maybeString){"use strict";return Object.prototype.toString.call(maybeString)==="[object String]"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFileOrInput=function(maybeFileOrInput){"use strict";if(window.File&&maybeFileOrInput instanceof File){return true}return qq.isInput(maybeFileOrInput)};qq.isInput=function(maybeInput){if(window.HTMLInputElement){if(maybeInput instanceof HTMLInputElement){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}else if(maybeInput.tagName){if(maybeInput.tagName.toLowerCase()==="input"){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&Object.prototype.toString.call(maybeBlob)==="[object Blob]"};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice!==undefined||File.prototype.webkitSlice!==undefined||File.prototype.mozSlice!==undefined)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}});return first};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{defaultResponseError:"Upload failure reason unknown",sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"misc_data",paramNames:{name:"qqblobname"}},paste:{targetElement:null,defaultName:"pasted_image"},camera:{ios:false}};qq.extend(this._options,o,true);this._handleCameraAccess();this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData=this._createUploadDataTracker();this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}if(this._options.paste.targetElement){this._pasteHandler=this._createPasteHandler()}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader "+qq.version+"] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader "+qq.version+"] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},getNetUploads:function(){return this._netUploaded},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._netUploadedOrQueued++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset();this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData.reset();if(this._pasteHandler){this._pasteHandler.reset()}},addFiles:function(filesOrInputs,params,endpoint){var self=this,verifiedFilesOrInputs=[],fileOrInputIndex,fileOrInput,fileIndex;if(filesOrInputs){if(!window.FileList||!(filesOrInputs instanceof FileList)){filesOrInputs=[].concat(filesOrInputs)}for(fileOrInputIndex=0;fileOrInputIndex=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||qq.supportedFeatures.deleteFileCors)},_onSubmitDelete:function(id,onSuccessCallback){if(this._isDeletePossible()){return this._handleCheckedCallback({name:"onSubmitDelete",callback:qq.bind(this._options.callbacks.onSubmitDelete,this,id),onSuccess:onSuccessCallback||qq.bind(this._deleteHandler.sendDelete,this,id,this.getUuid(id)),identifier:id})}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(id){this._uploadData.setStatus(id,qq.status.DELETING)},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);if(isError){this._uploadData.setStatus(id,qq.status.DELETE_FAILED);this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status,xhr)
+}else{this._uploadData.setStatus(id,qq.status.DELETED);this._netUploadedOrQueued--;this._netUploaded--;this._handler.expunge(id);this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){this._uploadData.setStatus(id,qq.status.UPLOADING)},_onInputChange:function(input){if(qq.supportedFeatures.ajaxUploading){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0&&this._netUploadedOrQueued+1>itemLimit){this._itemError("retryFailTooManyItems","");return false}this.log("Retrying upload for '"+fileName+"' (id: "+id+")...");this._filesInProgress.push(id);return true}else{this.log("'"+id+"' is not a valid file ID","error");return false}},_maybeParseAndSendUploadError:function(id,name,response,xhr){if(!response.success){if(xhr&&xhr.status!==200&&!response.error){this._options.callbacks.onError(id,name,"XHR returned response code "+xhr.status,xhr)}else{var errorReason=response.error?response.error:this._options.text.defaultResponseError;this._options.callbacks.onError(id,name,errorReason,xhr)}}},_prepareItemsForUpload:function(items,params,endpoint){var validationDescriptors=this._getValidationDescriptors(items);this._handleCheckedCallback({name:"onValidateBatch",callback:qq.bind(this._options.callbacks.onValidateBatch,this,validationDescriptors),onSuccess:qq.bind(this._onValidateBatchCallbackSuccess,this,validationDescriptors,items,params,endpoint),identifier:"batch validation"})},_upload:function(blobOrFileContainer,params,endpoint){var id=this._handler.add(blobOrFileContainer),name=this._handler.getName(id);this._uploadData.added(id);if(params){this.setParams(params,id)}if(endpoint){this.setEndpoint(endpoint,id)}this._handleCheckedCallback({name:"onSubmit",callback:qq.bind(this._options.callbacks.onSubmit,this,id,name),onSuccess:qq.bind(this._onSubmitCallbackSuccess,this,id,name),onFailure:qq.bind(this._fileOrBlobRejected,this,id,name),identifier:id})},_onSubmitCallbackSuccess:function(id,name){this._uploadData.setStatus(id,qq.status.SUBMITTED);this._onSubmit(id,name);this._options.callbacks.onSubmitted(id,name);if(this._options.autoUpload){if(!this._handler.upload(id)){this._uploadData.setStatus(id,qq.status.QUEUED)}}else{this._storeForLater(id)}},_storeForLater:function(id){this._storedIds.push(id)},_onValidateBatchCallbackSuccess:function(validationDescriptors,items,params,endpoint){var errorMessage,itemLimit=this._options.validation.itemLimit,proposedNetFilesUploadedOrQueued=this._netUploadedOrQueued+validationDescriptors.length;if(itemLimit===0||proposedNetFilesUploadedOrQueued<=itemLimit){if(items.length>0){this._handleCheckedCallback({name:"onValidate",callback:qq.bind(this._options.callbacks.onValidate,this,items[0]),onSuccess:qq.bind(this._onValidateCallbackSuccess,this,items,0,params,endpoint),onFailure:qq.bind(this._onValidateCallbackFailure,this,items,0,params,endpoint),identifier:"Item '"+items[0].name+"', size: "+items[0].size})}else{this._itemError("noFilesError","")}}else{errorMessage=this._options.messages.tooManyItemsError.replace(/\{netItems\}/g,proposedNetFilesUploadedOrQueued).replace(/\{itemLimit\}/g,itemLimit);this._batchError(errorMessage)}},_onValidateCallbackSuccess:function(items,index,params,endpoint){var nextIndex=index+1,validationDescriptor=this._getValidationDescriptor(items[index]),validItem=false;if(this._validateFileOrBlobData(items[index],validationDescriptor)){validItem=true;this._upload(items[index],params,endpoint)}this._maybeProcessNextItemAfterOnValidateCallback(validItem,items,nextIndex,params,endpoint)},_onValidateCallbackFailure:function(items,index,params,endpoint){var nextIndex=index+1;this._fileOrBlobRejected(undefined,items[0].name);this._maybeProcessNextItemAfterOnValidateCallback(false,items,nextIndex,params,endpoint)},_maybeProcessNextItemAfterOnValidateCallback:function(validItem,items,index,params,endpoint){var self=this;if(items.length>index){if(validItem||!this._options.validation.stopOnFirstInvalidFile){setTimeout(function(){var validationDescriptor=self._getValidationDescriptor(items[index]);self._handleCheckedCallback({name:"onValidate",callback:qq.bind(self._options.callbacks.onValidate,self,items[index]),onSuccess:qq.bind(self._onValidateCallbackSuccess,self,items,index,params,endpoint),onFailure:qq.bind(self._onValidateCallbackFailure,self,items,index,params,endpoint),identifier:"Item '"+validationDescriptor.name+"', size: "+validationDescriptor.size})},0)}}},_validateFileOrBlobData:function(item,validationDescriptor){var name=validationDescriptor.name,size=validationDescriptor.size,valid=true;if(this._options.callbacks.onValidate(validationDescriptor)===false){valid=false}if(qq.isFileOrInput(item)&&!this._isAllowedExtension(name)){this._itemError("typeError",name);valid=false}else if(size===0){this._itemError("emptyError",name);valid=false}else if(size&&this._options.validation.sizeLimit&&size>this._options.validation.sizeLimit){this._itemError("sizeError",name);valid=false}else if(size&&size999);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size!==undefined){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}},_handleCameraAccess:function(){if(this._options.camera.ios&&qq.ios()){this._options.multiple=false;if(this._options.validation.acceptFiles===null){this._options.validation.acceptFiles="image/*;capture=camera"}else{this._options.validation.acceptFiles+=",image/*;capture=camera"}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,droppedFiles=[],disposeSupport=new qq.DisposeSupport;options={dropZoneElements:[],hideDropZonesBeforeEnter:false,allowMultipleItems:true,classes:{dropActive:null},callbacks:new qq.DragAndDrop.callbacks};qq.extend(options,o,true);setupDragDrop();function uploadDroppedFiles(files){options.callbacks.dropLog("Grabbed "+files.length+" dropped files.");dz.dropDisabled(false);options.callbacks.processingDroppedFilesComplete(files)}function traverseFileTree(entry){var dirReader,i,parseEntryPromise=new qq.Promise;if(entry.isFile){entry.file(function(file){droppedFiles.push(file);parseEntryPromise.success()},function(fileError){options.callbacks.dropLog("Problem parsing '"+entry.fullPath+"'. FileError code "+fileError.code+".","error");parseEntryPromise.failure()})}else if(entry.isDirectory){dirReader=entry.createReader();dirReader.readEntries(function(entries){var entriesLeft=entries.length;for(i=0;i1&&!options.allowMultipleItems){options.callbacks.processingDroppedFilesComplete([]);options.callbacks.dropError("tooManyFilesError","");dz.dropDisabled(false);handleDataTransferPromise.failure()}else{droppedFiles=[];if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file",unsupportedBrowser:"Unrecoverable error - this browser does not permit file uploading of any kind."},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false,prependFiles:false},paste:{promptForName:false,namePromptMessage:"Please name this image"},showMessage:function(message){setTimeout(function(){window.alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=window.confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)},showPrompt:function(message,defaultValue){var promise=new qq.Promise,retVal=window.prompt(message,defaultValue);if(retVal!=null&&qq.trimStr(retVal).length>0){promise.success(retVal)}else{promise.failure("Undefined or invalid user-supplied value.")}return promise}},true);qq.extend(this._options,o,true);if(!qq.supportedFeatures.uploading||this._options.cors.expected&&!qq.supportedFeatures.uploadCors){this._options.element.innerHTML=""+this._options.messages.unsupportedBrowser+"
"}else{this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop();if(this._options.paste.targetElement&&this._options.paste.promptForName){this._setupPastePrompt()}this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0}};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop();this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dropZoneElements=this._options.dragAndDrop.extraDropzones,preventSelectFiles;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){dropZoneElements.push(this._find(this._options.element,"drop"))}return new qq.DragAndDrop({dropZoneElements:dropZoneElements,hideDropZonesBeforeEnter:this._options.dragAndDrop.hideDropzones,allowMultipleItems:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{processingDroppedFiles:function(){var input=self._button.getInput();qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)},processingDroppedFilesComplete:function(files){var input=self._button.getInput();qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles);if(files){self.addFiles(files)}},dropError:function(code,errorData){self._itemError(code,errorData)},dropLog:function(message,level){self.log(message,level)}}})},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){var item=this.getItemByFileId(id);if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}else{qq(item).addClass(this._classes.retryable);return false}},_onSubmitDelete:function(id){var onSuccessCallback=qq.bind(this._onSubmitDeleteSuccess,this,id);qq.FineUploaderBasic.prototype._onSubmitDelete.call(this,id,onSuccessCallback)},_onSubmitDeleteSuccess:function(id){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}if(this._options.display.prependFiles){this._prependItem(item)}else{this._listElement.appendChild(item)}this._filesInBatchAddedToUi+=1;if(this._options.display.fileSizeOnSubmit&&qq.supportedFeatures.ajaxUploading){this._displayFileSize(id)}},_prependItem:function(item){var parentEl=this._listElement,beforeEl=parentEl.firstChild;if(this._totalFilesInBatch>1&&this._filesInBatchAddedToUi>0){beforeEl=qq(parentEl).children()[this._filesInBatchAddedToUi-1].nextSibling}parentEl.insertBefore(item,beforeEl)},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=item.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")
+}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_itemError:function(code,name){var message=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(message)},_batchError:function(message){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments);this._options.showMessage(message)},_setupPastePrompt:function(){var self=this;this._options.callbacks.onPasteReceived=function(){var message=self._options.paste.namePromptMessage,defaultVal=self._options.paste.defaultName;return self._options.showPrompt(message,defaultVal)}},_fileOrBlobRejected:function(id,name){this._totalFilesInBatch-=1;qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this,arguments)},_prepareItemsForUpload:function(items,params,endpoint){this._totalFilesInBatch=items.length;this._filesInBatchAddedToUi=0;qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this,arguments)}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,handlerImpl,api;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){},onUuidChanged:function(id,newUuid){}};qq.extend(options,o);log=options.log;function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){var cancelRetVal=handlerImpl.cancel(id);if(qq.isPromise(cancelRetVal)){cancelRetVal.then(function(){cancelSuccess(id)})}else if(cancelRetVal!==false){cancelSuccess(id)}},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},reset:function(){log("Resetting upload handler");api.cancelAll();queue=[];handlerImpl.reset()},expunge:function(id){return handlerImpl.expunge(id)},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}};return api};qq.UploadHandlerForm=function(o,uploadCompleteCallback,onUuidChange,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},formHandlerInstanceId=qq.getUniqueId(),api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var iframeName=iframe.id,fileId=getFileIdForIframeName(iframeName);onloadCallbacks[uuids[fileId]]=callback;detachLoadEvents[fileId]=qq(iframe).attach("load",function(){if(inputs[fileId]){log("Received iframe load event for CORS upload request (iframe name "+iframeName+")");postMessageCallbackTimers[iframeName]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for iframe name "+iframeName;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(iframeName,function(message){log("Received the following window message: '"+message+"'");var response=parseResponse(getFileIdForIframeName(iframeName),message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){log("Handling response for iframe name "+iframeName);clearTimeout(postMessageCallbackTimers[iframeName]);delete postMessageCallbackTimers[iframeName];detachLoadEvent(iframeName);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(iframeName);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(id,iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHtml=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHtml);if(innerHtml&&innerHtml.match(/^ ');iframe.setAttribute("id",iframeName);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}function expungeFile(id){delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(getIframeName(id));if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}}function getFileIdForIframeName(iframeName){return iframeName.split("_")[0]}function getIframeName(fileId){return fileId+"_"+formHandlerInstanceId}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){if(api.isValid(id)){return inputs[id].value.replace(/.*(\/|\\)/,"")}else{log(id+" is not a valid item ID.","error")}},isValid:function(id){return inputs[id]!==undefined},reset:function(){inputs=[];uuids=[];detachLoadEvents={};formHandlerInstanceId=qq.getUniqueId()},expunge:function(id){return expungeFile(id)},getUuid:function(id){return uuids[id]},cancel:function(id){var onCancelRetVal=options.onCancel(id,api.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeFile(id)})}else if(onCancelRetVal!==false){expungeFile(id);return true}return false},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,api.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(id,iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove()}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,onUuidChange,logCallback){"use strict";var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.supportedFeatures.chunking,resumeEnabled=options.resume.enabled&&chunkFiles&&qq.supportedFeatures.resume,resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);if(fileState[id]){delete fileState[id].xhr}uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(id,xhr){var response;try{response=qq.parseJson(xhr.responseText);if(response.newUuid!==undefined){log("Server requested UUID change from '"+fileState[id].uuid+"' to '"+response.newUuid+"'");fileState[id].uuid=response.newUuid;onUuidChanged(id,response.newUuid)}}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(id,xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex){var currentChunkIndex;for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}uploadNextChunk(id)}function onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex);calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}function handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex){var name=api.getName(id),firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part),onResumeRetVal;onResumeRetVal=options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume));if(qq.isPromise(onResumeRetVal)){log("Waiting for onResume promise to be fulfilled for "+id);onResumeRetVal.then(function(){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)},function(){log("onResume promise fulfilled - failure indicated. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)})}else if(onResumeRetVal!==false){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)}else{log("onResume callback returned false. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}function handleFileChunkingUpload(id,retry){var firstChunkIndex=0,persistedChunkInfoForResume;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex)}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{uploadNextChunk(id)}}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}function expungeItem(id){var xhr=fileState[id].xhr;if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]}api={add:function(fileOrBlobData){var id,persistedChunkData,uuid=qq.getUniqueId();if(fileOrBlobData instanceof File){id=fileState.push({file:fileOrBlobData})-1}else if(qq.isBlob(fileOrBlobData.blob)){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}if(resumeEnabled){persistedChunkData=getPersistedChunkData(id);if(persistedChunkData){uuid=persistedChunkData.uuid}}fileState[id].uuid=uuid;return id},getName:function(id){if(api.isValid(id)){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}}else{log(id+" is not a valid item ID.","error")}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},expunge:function(id){return expungeItem(id)},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);if(this.isValid(id)){options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}}},cancel:function(id){var onCancelRetVal=options.onCancel(id,this.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeItem(id)})}else if(onCancelRetVal!==false){expungeItem(id);return true}return false},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};!function($){"use strict";var uploader,$el,init,dataStore,pluginOption,pluginOptions,addCallbacks,transformVariables,isValidCommand,delegateCommand;pluginOptions=["uploaderType"];init=function(options){if(options){var xformedOpts=transformVariables(options);addCallbacks(xformedOpts);if(pluginOption("uploaderType")==="basic"){uploader(new qq.FineUploaderBasic(xformedOpts))}else{uploader(new qq.FineUploader(xformedOpts))}}return $el};dataStore=function(key,val){var data=$el.data("fineuploader");if(val){if(data===undefined){data={}}data[key]=val;$el.data("fineuploader",data)}else{if(data===undefined){return null}return data[key]}};uploader=function(instanceToStore){return dataStore("uploader",instanceToStore)};pluginOption=function(option,optionVal){return dataStore(option,optionVal)};addCallbacks=function(transformedOpts){var callbacks=transformedOpts.callbacks={},uploaderInst=new qq.FineUploaderBasic;$.each(uploaderInst._options.callbacks,function(prop,func){var name,$callbackEl;name=/^on(\w+)/.exec(prop)[1];name=name.substring(0,1).toLowerCase()+name.substring(1);$callbackEl=$el;callbacks[prop]=function(){var args=Array.prototype.slice.call(arguments);return $callbackEl.triggerHandler(name,args)}})};transformVariables=function(source,dest){var xformed,arrayVals;if(dest===undefined){if(source.uploaderType!=="basic"){xformed={element:$el[0]}}else{xformed={}}}else{xformed=dest}$.each(source,function(prop,val){if($.inArray(prop,pluginOptions)>=0){pluginOption(prop,val)}else if(val instanceof $){xformed[prop]=val[0]}else if($.isPlainObject(val)){xformed[prop]={};transformVariables(val,xformed[prop])}else if($.isArray(val)){arrayVals=[];$.each(val,function(idx,arrayVal){if(arrayVal instanceof $){$.merge(arrayVals,arrayVal)}else{arrayVals.push(arrayVal)}});xformed[prop]=arrayVals}else{xformed[prop]=val}});if(dest===undefined){return xformed}};isValidCommand=function(command){return $.type(command)==="string"&&!command.match(/^_/)&&uploader()[command]!==undefined};delegateCommand=function(command){var xformedArgs=[],origArgs=Array.prototype.slice.call(arguments,1);transformVariables(origArgs,xformedArgs);return uploader()[command].apply(uploader(),xformedArgs)};$.fn.fineUploader=function(optionsOrCommand){var self=this,selfArgs=arguments,retVals=[];this.each(function(index,el){$el=$(el);if(uploader()&&isValidCommand(optionsOrCommand)){retVals.push(delegateCommand.apply(self,selfArgs));if(self.length===1){return false}}else if(typeof optionsOrCommand==="object"||!optionsOrCommand){init.apply(self,selfArgs)}else{$.error("Method "+optionsOrCommand+" does not exist on jQuery.fineUploader")}});if(retVals.length===1){return retVals[0]}else if(retVals.length>1){return retVals}return this}
+}(jQuery);!function($){"use strict";var rootDataKey="fineUploaderDnd",$el;function init(options){if(!options){options={}}options.dropZoneElements=[$el];var xformedOpts=transformVariables(options);addCallbacks(xformedOpts);dnd(new qq.DragAndDrop(xformedOpts));return $el}function dataStore(key,val){var data=$el.data(rootDataKey);if(val){if(data===undefined){data={}}data[key]=val;$el.data(rootDataKey,data)}else{if(data===undefined){return null}return data[key]}}function dnd(instanceToStore){return dataStore("dndInstance",instanceToStore)}function addCallbacks(transformedOpts){var callbacks=transformedOpts.callbacks={},dndInst=new qq.FineUploaderBasic;$.each(new qq.DragAndDrop.callbacks,function(prop,func){var name=prop,$callbackEl;$callbackEl=$el;callbacks[prop]=function(){var args=Array.prototype.slice.call(arguments),jqueryHandlerResult=$callbackEl.triggerHandler(name,args);return jqueryHandlerResult}})}function transformVariables(source,dest){var xformed,arrayVals;if(dest===undefined){xformed={}}else{xformed=dest}$.each(source,function(prop,val){if(val instanceof $){xformed[prop]=val[0]}else if($.isPlainObject(val)){xformed[prop]={};transformVariables(val,xformed[prop])}else if($.isArray(val)){arrayVals=[];$.each(val,function(idx,arrayVal){if(arrayVal instanceof $){$.merge(arrayVals,arrayVal)}else{arrayVals.push(arrayVal)}});xformed[prop]=arrayVals}else{xformed[prop]=val}});if(dest===undefined){return xformed}}function isValidCommand(command){return $.type(command)==="string"&&command==="dispose"&&dnd()[command]!==undefined}function delegateCommand(command){var xformedArgs=[],origArgs=Array.prototype.slice.call(arguments,1);transformVariables(origArgs,xformedArgs);return dnd()[command].apply(dnd(),xformedArgs)}$.fn.fineUploaderDnd=function(optionsOrCommand){var self=this,selfArgs=arguments,retVals=[];this.each(function(index,el){$el=$(el);if(dnd()&&isValidCommand(optionsOrCommand)){retVals.push(delegateCommand.apply(self,selfArgs));if(self.length===1){return false}}else if(typeof optionsOrCommand==="object"||!optionsOrCommand){init.apply(self,selfArgs)}else{$.error("Method "+optionsOrCommand+" does not exist in Fine Uploader's DnD module.")}});if(retVals.length===1){return retVals[0]}else if(retVals.length>1){return retVals}return this}}(jQuery);
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.0/fineuploader.css b/ajax/libs/file-uploader/3.6.0/fineuploader.css
new file mode 100644
index 000000000..11a436a06
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.0/fineuploader.css
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2013, Widen Enterprises info@fineuploader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+.qq-uploader {
+ position: relative;
+ width: 100%;
+}
+.qq-upload-button {
+ display: block;
+ width: 105px;
+ padding: 7px 0;
+ text-align: center;
+ background: #880000;
+ border-bottom: 1px solid #DDD;
+ color: #FFF;
+}
+.qq-upload-button-hover {
+ background: #CC0000;
+}
+.qq-upload-button-focus {
+ outline: 1px dotted #000000;
+}
+.qq-upload-drop-area, .qq-upload-extra-drop-area {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ min-height: 30px;
+ z-index: 2;
+ background: #FF9797;
+ text-align: center;
+}
+.qq-upload-drop-area span {
+ display: block;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ margin-top: -8px;
+ font-size: 16px;
+}
+.qq-upload-extra-drop-area {
+ position: relative;
+ margin-top: 50px;
+ font-size: 16px;
+ padding-top: 30px;
+ height: 20px;
+ min-height: 40px;
+}
+.qq-upload-drop-area-active {
+ background: #FF7171;
+}
+.qq-upload-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+.qq-upload-list li {
+ margin: 0;
+ padding: 9px;
+ line-height: 15px;
+ font-size: 16px;
+ background-color: #FFF0BD;
+}
+.qq-upload-file, .qq-upload-spinner, .qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text, .qq-upload-finished, .qq-upload-delete {
+ margin-right: 12px;
+}
+.qq-upload-file {
+}
+.qq-upload-spinner {
+ display: inline-block;
+ background: url("loading.gif");
+ width: 15px;
+ height: 15px;
+ vertical-align: text-bottom;
+}
+.qq-drop-processing {
+ display: none;
+}
+.qq-drop-processing-spinner {
+ display: inline-block;
+ background: url("processing.gif");
+ width: 24px;
+ height: 24px;
+ vertical-align: text-bottom;
+}
+.qq-upload-finished {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-retry, .qq-upload-delete {
+ display: none;
+ color: #000000;
+}
+.qq-upload-cancel, .qq-upload-delete {
+ color: #000000;
+}
+.qq-upload-retryable .qq-upload-retry {
+ display: inline;
+}
+.qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-delete {
+ font-size: 12px;
+ font-weight: normal;
+}
+.qq-upload-failed-text {
+ display: none;
+ font-style: italic;
+ font-weight: bold;
+}
+.qq-upload-failed-icon {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-fail .qq-upload-failed-text {
+ display: inline;
+}
+.qq-upload-retrying .qq-upload-failed-text {
+ display: inline;
+ color: #D60000;
+}
+.qq-upload-list li.qq-upload-success {
+ background-color: #5DA30C;
+ color: #FFFFFF;
+}
+.qq-upload-list li.qq-upload-fail {
+ background-color: #D60000;
+ color: #FFFFFF;
+}
+.qq-progress-bar {
+ background: -moz-linear-gradient(top, rgba(30,87,153,1) 0%, rgba(41,137,216,1) 50%, rgba(32,124,202,1) 51%, rgba(125,185,232,1) 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(30,87,153,1)), color-stop(50%,rgba(41,137,216,1)), color-stop(51%,rgba(32,124,202,1)), color-stop(100%,rgba(125,185,232,1))); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* IE10+ */
+ background: linear-gradient(to bottom, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* W3C */
+ width: 0%;
+ height: 15px;
+ border-radius: 6px;
+ margin-bottom: 3px;
+ display: none;
+}
diff --git a/ajax/libs/file-uploader/3.6.0/fineuploader.js b/ajax/libs/file-uploader/3.6.0/fineuploader.js
new file mode 100644
index 000000000..d5d18e368
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.0/fineuploader.js
@@ -0,0 +1,4717 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: -unstable-
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity != null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isArray = function(variable) {
+ "use strict";
+ return Object.prototype.toString.call(variable) === "[object Array]";
+}
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+ if (window.File && maybeFileOrInput instanceof File) {
+ return true;
+ }
+
+ return qq.isInput(maybeFileOrInput);
+};
+
+qq.isInput = function(maybeInput) {
+ if (window.HTMLInputElement) {
+ if (maybeInput instanceof HTMLInputElement) {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ else if (maybeInput.tagName) {
+ if (maybeInput.tagName.toLowerCase() === 'input') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && Object.prototype.toString.call(maybeBlob) === '[object Blob]';
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice !== undefined || File.prototype.webkitSlice !== undefined || File.prototype.mozSlice !== undefined);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+
+ return first;
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+qq.ios = function() {
+ "use strict";
+ return navigator.userAgent.indexOf("iPad") !== -1
+ || navigator.userAgent.indexOf("iPod") !== -1
+ || navigator.userAgent.indexOf("iPhone") !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(objOrArray, callback) {
+ "use strict";
+ var keyOrIndex, retVal;
+ if (objOrArray) {
+ if (qq.isArray(objOrArray)) {
+ for (keyOrIndex = 0; keyOrIndex < objOrArray.length; keyOrIndex++) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ else {
+ for (keyOrIndex in objOrArray) {
+ if (Object.prototype.hasOwnProperty.call(objOrArray, keyOrIndex)) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+ }
+};
+
+//include any args that should be passed to the new function after the context arg
+qq.bind = function(oldFunc, context) {
+ if (qq.isFunction(oldFunc)) {
+ var args = Array.prototype.slice.call(arguments, 2);
+
+ return function() {
+ if (arguments.length) {
+ args = args.concat(Array.prototype.slice.call(arguments))
+ }
+ return oldFunc.apply(context, args);
+ };
+ }
+
+ throw new Error("first parameter must be a function!");
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ cookie;
+
+ qq.each(ca, function(idx, part) {
+ var cookiePart = part;
+ while (cookiePart.charAt(0)==' ') {
+ cookiePart = cookiePart.substring(1, cookiePart.length);
+ }
+
+ if (cookiePart.indexOf(nameEQ) === 0) {
+ cookie = cookiePart.substring(nameEQ.length, cookiePart.length);
+ return false;
+ }
+ });
+
+ return cookie;
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+qq.version="-unstable-";qq.supportedFeatures = (function() {
+ var supportsUploading,
+ supportsAjaxFileUploading,
+ supportsFolderDrop,
+ supportsChunking,
+ supportsResume,
+ supportsUploadViaPaste,
+ supportsUploadCors,
+ supportsDeleteFileCors;
+
+
+ function testSupportsFileInputElement() {
+ var supported = true,
+ tempInput;
+
+ try {
+ tempInput = document.createElement('input');
+ tempInput.type = 'file';
+ qq(tempInput).hide();
+
+ if(tempInput.disabled) {
+ supported = false;
+ }
+ }
+ catch(ex) {
+ supported = false;
+ }
+
+ return supported;
+ }
+
+ //only way to test for Filesystem API support since webkit does not expose the DataTransfer interface
+ function isChrome21OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[2][1-9]|Chrome\/[3-9][0-9]/) !== undefined;
+ }
+
+ //only way to test for complete Clipboard API support at this time
+ function isChrome14OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/) !== undefined;
+ }
+
+
+ supportsUploading = testSupportsFileInputElement();
+
+ supportsAjaxFileUploading = supportsUploading && qq.isXhrUploadSupported();
+
+ supportsFolderDrop = supportsAjaxFileUploading && isChrome21OrHigher();
+
+ supportsChunking = supportsAjaxFileUploading && qq.isFileChunkingSupported();
+
+ supportsResume = supportsAjaxFileUploading && supportsChunking && qq.areCookiesEnabled();
+
+ supportsUploadViaPaste = supportsAjaxFileUploading && isChrome14OrHigher();
+
+ supportsUploadCors = supportsUploading && (window.postMessage !== undefined || supportsAjaxFileUploading);
+
+ supportsDeleteFileCors = supportsAjaxFileUploading;
+
+
+ return {
+ uploading: supportsUploading,
+ ajaxUploading: supportsAjaxFileUploading,
+ fileDrop: supportsAjaxFileUploading, //NOTE: will also return true for touch-only devices. It's not currently possible to accurately test for touch-only devices
+ folderDrop: supportsFolderDrop,
+ chunking: supportsChunking,
+ resume: supportsResume,
+ uploadCustomHeaders: supportsAjaxFileUploading,
+ uploadNonMultipart: supportsAjaxFileUploading,
+ itemSizeValidation: supportsAjaxFileUploading,
+ uploadViaPaste: supportsUploadViaPaste,
+ progressBar: supportsAjaxFileUploading,
+ uploadCors: supportsUploadCors,
+ deleteFileCors: supportsDeleteFileCors,
+ canDetermineSize: supportsAjaxFileUploading
+ }
+
+}());
+/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallbacks = [],
+ failureCallbacks = [],
+ doneCallbacks = [],
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ if (onSuccess) {
+ successCallbacks.push(onSuccess);
+ }
+ if (onFailure) {
+ failureCallbacks.push(onFailure);
+ }
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+
+ return this;
+ },
+
+ done: function(callback) {
+ if (state === 0) {
+ doneCallbacks.push(callback);
+ }
+ else {
+ callback();
+ }
+
+ return this;
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallbacks.length) {
+ qq.each(successCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallbacks.length) {
+ qq.each(failureCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ }
+ };
+};
+
+qq.isPromise = function(maybePromise) {
+ return maybePromise && maybePromise.then && maybePromise.done;
+};/*globals qq*/
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ disposeSupport = new qq.DisposeSupport(),
+ options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input) {},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+
+ qq.extend(options, o);
+
+ // make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};qq.UploadData = function(uploaderProxy) {
+ var data = [],
+ byId = {},
+ byUuid = {},
+ byStatus = {},
+ api;
+
+ function getDataByIds(ids) {
+ if (qq.isArray(ids)) {
+ var entries = [];
+
+ qq.each(ids, function(idx, id) {
+ entries.push(data[byId[id]]);
+ });
+
+ return entries;
+ }
+
+ return data[byId[ids]];
+ }
+
+ function getDataByUuids(uuids) {
+ if (qq.isArray(uuids)) {
+ var entries = [];
+
+ qq.each(uuids, function(idx, uuid) {
+ entries.push(data[byUuid[uuid]]);
+ });
+
+ return entries;
+ }
+
+ return data[byUuid[uuids]];
+ }
+
+ function getDataByStatus(status) {
+ var statusResults = [],
+ statuses = [].concat(status);
+
+ qq.each(statuses, function(index, statusEnum) {
+ var statusResultIndexes = byStatus[statusEnum];
+
+ if (statusResultIndexes !== undefined) {
+ qq.each(statusResultIndexes, function(i, dataIndex) {
+ statusResults.push(data[dataIndex]);
+ });
+ }
+ });
+
+ return statusResults;
+ }
+
+ api = {
+ added: function(id) {
+ var uuid = uploaderProxy.getUuid(id),
+ name = uploaderProxy.getName(id),
+ size = uploaderProxy.getSize(id),
+ status = qq.status.SUBMITTING;
+
+ var index = data.push({
+ id: id,
+ name: name,
+ uuid: uuid,
+ size: size,
+ status: status
+ }) - 1;
+
+ byId[id] = index;
+
+ byUuid[uuid] = index;
+
+ if (byStatus[status] === undefined) {
+ byStatus[status] = [];
+ }
+ byStatus[status].push(index);
+
+ uploaderProxy.onStatusChange(id, undefined, status);
+ },
+
+ retrieve: function(optionalFilter) {
+ if (qq.isObject(optionalFilter) && data.length) {
+ if (optionalFilter.id !== undefined) {
+ return getDataByIds(optionalFilter.id);
+ }
+
+ else if (optionalFilter.uuid !== undefined) {
+ return getDataByUuids(optionalFilter.uuid);
+ }
+
+ else if (optionalFilter.status) {
+ return getDataByStatus(optionalFilter.status);
+ }
+ }
+ else {
+ return qq.extend([], data, true);
+ }
+ },
+
+ reset: function() {
+ data = [];
+ byId = {};
+ byUuid = {};
+ byStatus = {};
+ },
+
+ setStatus: function(id, newStatus) {
+ var dataIndex = byId[id],
+ oldStatus = data[dataIndex].status,
+ byStatusOldStatusIndex = qq.indexOf(byStatus[oldStatus], dataIndex);
+
+ byStatus[oldStatus].splice(byStatusOldStatusIndex, 1);
+
+ data[dataIndex].status = newStatus;
+
+ if (byStatus[newStatus] === undefined) {
+ byStatus[newStatus] = [];
+ }
+ byStatus[newStatus].push(dataIndex);
+
+ uploaderProxy.onStatusChange(id, oldStatus, newStatus);
+ },
+
+ uuidChanged: function(id, newUuid) {
+ var dataIndex = byId[id],
+ oldUuid = data[dataIndex].uuid;
+
+ data[dataIndex].uuid = newUuid;
+ byUuid[newUuid] = dataIndex;
+ delete byUuid[oldUuid];
+ }
+ };
+
+ return api;
+};
+
+qq.status = {
+ SUBMITTING: "submitting",
+ SUBMITTED: "submitted",
+ REJECTED: "rejected",
+ QUEUED: "queued",
+ CANCELED: "canceled",
+ UPLOADING: "uploading",
+ UPLOAD_RETRYING: "retrying upload",
+ UPLOAD_SUCCESSFUL: "upload successful",
+ UPLOAD_FAILED: "upload failed",
+ DELETE_FAILED: "delete failed",
+ DELETING: "deleting",
+ DELETED: "deleted"
+};qq.FineUploaderBasic = function(o) {
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true,
+ acceptFiles: null
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON, maybeXhr){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){},
+ onPasteReceived: function(blob) {},
+ onStatusChange: function(id, oldStatus, newStatus) {}
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ defaultResponseError: "Upload failure reason unknown",
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'misc_data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ },
+ camera: {
+ ios: false
+ }
+ };
+
+ qq.extend(this._options, o, true);
+
+ this._handleCameraAccess();
+
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData = this._createUploadDataTracker();
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader ' + qq.version + '] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader ' + qq.version + '] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function() {
+ return this._filesInProgress.length;
+ },
+ getNetUploads: function() {
+ return this._netUploaded;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netUploadedOrQueued++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData.reset();
+
+ if (this._pasteHandler) {
+ this._pasteHandler.reset();
+ }
+ },
+ addFiles: function(filesOrInputs, params, endpoint) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ fileOrInputIndex, fileOrInput, fileIndex;
+
+ if (filesOrInputs) {
+ if (!window.FileList || !(filesOrInputs instanceof FileList)) {
+ filesOrInputs = [].concat(filesOrInputs);
+ }
+
+ for (fileOrInputIndex = 0; fileOrInputIndex < filesOrInputs.length; fileOrInputIndex+=1) {
+ fileOrInput = filesOrInputs[fileOrInputIndex];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ if (qq.isInput(fileOrInput) && qq.supportedFeatures.ajaxUploading) {
+ for (fileIndex = 0; fileIndex < fileOrInput.files.length; fileIndex++) {
+ verifiedFilesOrInputs.push(fileOrInput.files[fileIndex]);
+ }
+ }
+ else {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Received ' + verifiedFilesOrInputs.length + ' files or inputs.');
+ this._prepareItemsForUpload(verifiedFilesOrInputs, params, endpoint);
+ }
+ },
+ addBlobs: function(blobDataOrArray, params, endpoint) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._prepareItemsForUpload(verifiedBlobDataList, params, endpoint);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ doesExist: function(fileOrBlobId) {
+ return this._handler.isValid(fileOrBlobId);
+ },
+ getUploads: function(optionalFilter) {
+ return this._uploadData.retrieve(optionalFilter);
+ },
+ _handleCheckedCallback: function(details) {
+ var self = this,
+ callbackRetVal = details.callback();
+
+ if (qq.isPromise(callbackRetVal)) {
+ this.log(details.name + " - waiting for " + details.name + " promise to be fulfilled for " + details.identifier);
+ return callbackRetVal.then(
+ function(successParam) {
+ self.log(details.name + " promise success for " + details.identifier);
+ details.onSuccess(successParam);
+ },
+ function() {
+ if (details.onFailure) {
+ self.log(details.name + " promise failure for " + details.identifier);
+ details.onFailure();
+ }
+ else {
+ self.log(details.name + " promise failure for " + details.identifier);
+ }
+ });
+ }
+
+ if (callbackRetVal !== false) {
+ details.onSuccess(callbackRetVal);
+ }
+ else {
+ if (details.onFailure) {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Invoking failure callback.")
+ details.onFailure();
+ }
+ else {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Will not proceed.")
+ }
+ }
+
+ return callbackRetVal;
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.supportedFeatures.ajaxUploading,
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result, xhr);
+ },
+ onCancel: function(id, name) {
+ return self._handleCheckedCallback({
+ name: "onCancel",
+ callback: qq.bind(self._options.callbacks.onCancel, self, id, name),
+ onSuccess: qq.bind(self._onCancel, self, id, name),
+ identifier: id
+ });
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ onUuidChanged: function(id, newUuid) {
+ self._uploadData.uuidChanged(id, newUuid);
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ self._handleCheckedCallback({
+ name: "onPasteReceived",
+ callback: qq.bind(self._options.callbacks.onPasteReceived, self, blob),
+ onSuccess: qq.bind(self._handlePasteSuccess, self, blob),
+ identifier: "pasted image"
+ });
+ }
+ }
+ });
+ },
+ _createUploadDataTracker: function() {
+ var self = this;
+
+ return new qq.UploadData({
+ getName: function(id) {
+ return self.getName(id);
+ },
+ getUuid: function(id) {
+ return self.getUuid(id);
+ },
+ getSize: function(id) {
+ return self.getSize(id);
+ },
+ onStatusChange: function(id, oldStatus, newStatus) {
+ self._options.callbacks.onStatusChange(id, oldStatus, newStatus);
+ }
+ });
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total) {
+ //nothing to do yet in core uploader
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_FAILED);
+ this._netUploadedOrQueued--;
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_SUCCESSFUL);
+ this._netUploaded++;
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.CANCELED);
+
+ this._netUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected || qq.supportedFeatures.deleteFileCors));
+ },
+ _onSubmitDelete: function(id, onSuccessCallback) {
+ if (this._isDeletePossible()) {
+ return this._handleCheckedCallback({
+ name: "onSubmitDelete",
+ callback: qq.bind(this._options.callbacks.onSubmitDelete, this, id),
+ onSuccess: onSuccessCallback || qq.bind(this._deleteHandler.sendDelete, this, id, this.getUuid(id)),
+ identifier: id
+ });
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(id) {
+ this._uploadData.setStatus(id, qq.status.DELETING);
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this._uploadData.setStatus(id, qq.status.DELETE_FAILED);
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.DELETED);
+ this._netUploadedOrQueued--;
+ this._netUploaded--;
+ this._handler.expunge(id);
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.UPLOADING);
+ },
+ _onInputChange: function(input){
+ if (qq.supportedFeatures.ajaxUploading) {
+ this.addFiles(input.files);
+ }
+ else {
+ this.addFiles(input);
+ }
+
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems", "");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : this._options.text.defaultResponseError;
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ var validationDescriptors = this._getValidationDescriptors(items);
+
+ this._handleCheckedCallback({
+ name: "onValidateBatch",
+ callback: qq.bind(this._options.callbacks.onValidateBatch, this, validationDescriptors),
+ onSuccess: qq.bind(this._onValidateBatchCallbackSuccess, this, validationDescriptors, items, params, endpoint),
+ identifier: "batch validation"
+ });
+ },
+ _upload: function(blobOrFileContainer, params, endpoint) {
+ var id = this._handler.add(blobOrFileContainer),
+ name = this._handler.getName(id);
+
+ this._uploadData.added(id);
+
+ if (params) {
+ this.setParams(params, id);
+ }
+
+ if (endpoint) {
+ this.setEndpoint(endpoint, id);
+ }
+
+ this._handleCheckedCallback({
+ name: "onSubmit",
+ callback: qq.bind(this._options.callbacks.onSubmit, this, id, name),
+ onSuccess: qq.bind(this._onSubmitCallbackSuccess, this, id, name),
+ onFailure: qq.bind(this._fileOrBlobRejected, this, id, name),
+ identifier: id
+ });
+ },
+ _onSubmitCallbackSuccess: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.SUBMITTED);
+
+ this._onSubmit(id, name);
+ this._options.callbacks.onSubmitted(id, name);
+
+ if (this._options.autoUpload) {
+ if (!this._handler.upload(id)) {
+ this._uploadData.setStatus(id, qq.status.QUEUED);
+ }
+ }
+ else {
+ this._storeForLater(id);
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _onValidateBatchCallbackSuccess: function(validationDescriptors, items, params, endpoint) {
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netUploadedOrQueued + validationDescriptors.length;
+
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ if (items.length > 0) {
+ this._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(this._options.callbacks.onValidate, this, items[0]),
+ onSuccess: qq.bind(this._onValidateCallbackSuccess, this, items, 0, params, endpoint),
+ onFailure: qq.bind(this._onValidateCallbackFailure, this, items, 0, params, endpoint),
+ identifier: "Item '" + items[0].name + "', size: " + items[0].size
+ });
+ }
+ else {
+ this._itemError("noFilesError", "");
+ }
+ }
+ else {
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ },
+ _onValidateCallbackSuccess: function(items, index, params, endpoint) {
+ var nextIndex = index+1,
+ validationDescriptor = this._getValidationDescriptor(items[index]),
+ validItem = false;
+
+ if (this._validateFileOrBlobData(items[index], validationDescriptor)) {
+ validItem = true;
+ this._upload(items[index], params, endpoint);
+ }
+
+ this._maybeProcessNextItemAfterOnValidateCallback(validItem, items, nextIndex, params, endpoint);
+ },
+ _onValidateCallbackFailure: function(items, index, params, endpoint) {
+ var nextIndex = index+ 1;
+
+ this._fileOrBlobRejected(undefined, items[0].name);
+
+ this._maybeProcessNextItemAfterOnValidateCallback(false, items, nextIndex, params, endpoint);
+ },
+ _maybeProcessNextItemAfterOnValidateCallback: function(validItem, items, index, params, endpoint) {
+ var self = this;
+
+ if (items.length > index) {
+ if (validItem || !this._options.validation.stopOnFirstInvalidFile) {
+ //use setTimeout to prevent a stack overflow with a large number of files in the batch & non-promissory callbacks
+ setTimeout(function() {
+ var validationDescriptor = self._getValidationDescriptor(items[index]);
+
+ self._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(self._options.callbacks.onValidate, self, items[index]),
+ onSuccess: qq.bind(self._onValidateCallbackSuccess, self, items, index, params, endpoint),
+ onFailure: qq.bind(self._onValidateCallbackFailure, self, items, index, params, endpoint),
+ identifier: "Item '" + validationDescriptor.name + "', size: " + validationDescriptor.size
+ });
+ }, 0);
+ }
+ }
+ },
+ _validateFileOrBlobData: function(item, validationDescriptor) {
+ var name = validationDescriptor.name,
+ size = validationDescriptor.size,
+ valid = true;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ valid = false;
+ }
+
+ if (qq.isFileOrInput(item) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ valid = false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ valid = false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ valid = false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ valid = false;
+ }
+
+ if (!valid) {
+ this._fileOrBlobRejected(undefined, name);
+ }
+
+ return valid;
+ },
+ _fileOrBlobRejected: function(id, name) {
+ if (id !== undefined) {
+ this._uploadData.setStatus(id, qq.status.REJECTED);
+ }
+ },
+ _itemError: function(code, nameOrNames) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ names = [].concat(nameOrNames),
+ name = names[0],
+ extensionsForMessage, placeholderMatch;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ placeholderMatch = message.match(/(\{\w+\})/g);
+ if (placeholderMatch !== null) {
+ qq.each(placeholderMatch, function(idx, placeholder) {
+ r(placeholder, names[idx]);
+ });
+ }
+
+ this._options.callbacks.onError(null, name, message, undefined);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message, undefined);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1000;
+ i++;
+ } while (bytes > 999);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size !== undefined) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ },
+ _handleCameraAccess: function() {
+ if (this._options.camera.ios && qq.ios()) {
+ this._options.multiple = false;
+
+ if (this._options.validation.acceptFiles === null) {
+ this._options.validation.acceptFiles = "image/*;capture=camera";
+ }
+ else {
+ this._options.validation.acceptFiles += ",image/*;capture=camera";
+ }
+ }
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz,
+ droppedFiles = [],
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropZoneElements: [],
+ hideDropZonesBeforeEnter: false,
+ allowMultipleItems: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: new qq.DragAndDrop.callbacks()
+ };
+
+ qq.extend(options, o, true);
+
+ setupDragDrop();
+
+ function uploadDroppedFiles(files) {
+ options.callbacks.dropLog('Grabbed ' + files.length + " dropped files.");
+ dz.dropDisabled(false);
+ options.callbacks.processingDroppedFilesComplete(files);
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i,
+ parseEntryPromise = new qq.Promise();
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ droppedFiles.push(file);
+ parseEntryPromise.success();
+ },
+ function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+ else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ var entriesLeft = entries.length;
+
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]).done(function() {
+ entriesLeft-=1;
+
+ if (entriesLeft === 0) {
+ parseEntryPromise.success();
+ }
+ });
+ }
+
+ if (!entries.length) {
+ parseEntryPromise.success();
+ }
+ }, function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+
+ return parseEntryPromise;
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry,
+ pendingFolderPromises = [],
+ handleDataTransferPromise = new qq.Promise();
+
+ options.callbacks.processingDroppedFiles();
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.allowMultipleItems) {
+ options.callbacks.processingDroppedFilesComplete([]);
+ options.callbacks.dropError('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ handleDataTransferPromise.failure();
+ }
+ else {
+ droppedFiles = [];
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ }
+
+ else {
+ pendingFolderPromises.push(traverseFileTree(entry).done(function() {
+ pendingFolderPromises.pop();
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }));
+ }
+ }
+ }
+ }
+ else {
+ droppedFiles = dataTransfer.files;
+ }
+
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }
+
+ return handleDataTransferPromise;
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer).done(function() {
+ uploadDroppedFiles(droppedFiles);
+ });
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ var dropZones = options.dropZoneElements;
+
+ qq.each(dropZones, function(idx, dropZone) {
+ setupDropzone(dropZone);
+ })
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (dropZones.length && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).css({display: 'block'});
+ });
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropZonesBeforeEnter && qq.FineUploader.prototype._leaving_document_out(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setupExtraDropzone: function(element) {
+ options.dropZoneElements.push(element);
+ setupDropzone(element);
+ },
+
+ removeDropzone: function(element) {
+ var i,
+ dzs = options.dropZoneElements;
+
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+qq.DragAndDrop.callbacks = function() {
+ return {
+ processingDroppedFiles: function() {},
+ processingDroppedFilesComplete: function(files) {},
+ dropError: function(code, errorSpecifics) {
+ qq.log("Drag & drop error code '" + code + " with these specifics: '" + errorSpecifics + "'", "error");
+ },
+ dropLog: function(message, level) {
+ qq.log(message, level);
+ }
+ }
+}
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file",
+ unsupportedBrowser: "Unrecoverable error - this browser does not permit file uploading of any kind."
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false,
+ prependFiles: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+
+ if (!qq.supportedFeatures.uploading || (this._options.cors.expected && !qq.supportedFeatures.uploadCors)) {
+ this._options.element.innerHTML = "" + this._options.messages.unsupportedBrowser + "
"
+ }
+ else {
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dropZoneElements = this._options.dragAndDrop.extraDropzones,
+ preventSelectFiles;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ dropZoneElements.push(this._find(this._options.element, 'drop'));
+ }
+
+ return new qq.DragAndDrop({
+ dropZoneElements: dropZoneElements,
+ hideDropZonesBeforeEnter: this._options.dragAndDrop.hideDropzones,
+ allowMultipleItems: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ processingDroppedFiles: function() {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ },
+ processingDroppedFilesComplete: function(files) {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ dropError: function(code, errorData) {
+ self._itemError(code, errorData);
+ },
+ dropLog: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type) {
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name) {
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ var onSuccessCallback = qq.bind(this._onSubmitDeleteSuccess, this, id);
+
+ qq.FineUploaderBasic.prototype._onSubmitDelete.call(this, id, onSuccessCallback);
+ },
+ _onSubmitDeleteSuccess: function(id) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ if (this._options.display.prependFiles) {
+ this._prependItem(item);
+ }
+ else {
+ this._listElement.appendChild(item);
+ }
+ this._filesInBatchAddedToUi += 1;
+
+ if (this._options.display.fileSizeOnSubmit && qq.supportedFeatures.ajaxUploading) {
+ this._displayFileSize(id);
+ }
+ },
+ _prependItem: function(item) {
+ var parentEl = this._listElement,
+ beforeEl = parentEl.firstChild;
+
+ if (this._totalFilesInBatch > 1 && this._filesInBatchAddedToUi > 0) {
+ beforeEl = qq(parentEl).children()[this._filesInBatchAddedToUi - 1].nextSibling;
+
+ }
+
+ parentEl.insertBefore(item, beforeEl);
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = item.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ },
+ _fileOrBlobRejected: function(id, name) {
+ this._totalFilesInBatch -= 1;
+ qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this, arguments);
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ this._totalFilesInBatch = items.length;
+ this._filesInBatchAddedToUi = 0;
+ qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this, arguments);
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, handlerImpl, api;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){},
+ onUuidChanged: function(id, newUuid){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.supportedFeatures.ajaxUploading) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, options.onUuidChanged, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, options.onUuidChanged, log);
+ }
+
+ function cancelSuccess(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ dequeue(id);
+ }
+
+
+ api = {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ handlerImpl.upload(id);
+ return true;
+ }
+
+ return false;
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ var cancelRetVal = handlerImpl.cancel(id);
+
+ if (qq.isPromise(cancelRetVal)) {
+ cancelRetVal.then(function() {
+ cancelSuccess(id);
+ });
+ }
+ else if (cancelRetVal !== false) {
+ cancelSuccess(id);
+ }
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ api.cancelAll();
+ queue = [];
+ handlerImpl.reset();
+ },
+ expunge: function(id) {
+ return handlerImpl.expunge(id);
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, onUuidChange, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ formHandlerInstanceId = qq.getUniqueId(),
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var iframeName = iframe.id,
+ fileId = getFileIdForIframeName(iframeName);
+
+ onloadCallbacks[uuids[fileId]] = callback;
+
+ detachLoadEvents[fileId] = qq(iframe).attach('load', function() {
+ if (inputs[fileId]) {
+ log("Received iframe load event for CORS upload request (iframe name " + iframeName + ")");
+
+ postMessageCallbackTimers[iframeName] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for iframe name " + iframeName;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(iframeName, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = parseResponse(getFileIdForIframeName(iframeName), message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ log("Handling response for iframe name " + iframeName);
+ clearTimeout(postMessageCallbackTimers[iframeName]);
+ delete postMessageCallbackTimers[iframeName];
+
+ detachLoadEvent(iframeName);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(iframeName);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(id, iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHtml = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHtml);
+ //plain text response may be wrapped in tag
+ if (innerHtml && innerHtml.match(/^ ');
+
+ iframe.setAttribute('id', iframeName);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+ function expungeFile(id) {
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(getIframeName(id));
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ }
+
+ function getFileIdForIframeName(iframeName) {
+ return iframeName.split("_")[0];
+ }
+
+ function getIframeName(fileId) {
+ return fileId + "_" + formHandlerInstanceId;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ formHandlerInstanceId = qq.getUniqueId();
+ },
+ expunge: function(id) {
+ return expungeFile(id);
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, api.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeFile(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeFile(id);
+ return true;
+ }
+
+ return false;
+ },
+
+ upload: function(id) {
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, api.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(id, iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, onUuidChange, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.supportedFeatures.chunking,
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.supportedFeatures.resume,
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+ options.onComplete(id, name, response, xhr);
+
+ if (fileState[id]) {
+ delete fileState[id].xhr;
+ }
+
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(id, xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+
+ if (response.newUuid !== undefined) {
+ log("Server requested UUID change from '" + fileState[id].uuid + "' to '" + response.newUuid + "'");
+ fileState[id].uuid = response.newUuid;
+ onUuidChanged(id, response.newUuid);
+ }
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(id, xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex) {
+ var currentChunkIndex;
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+
+ function handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex) {
+ var name = api.getName(id),
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part),
+ onResumeRetVal;
+
+ onResumeRetVal = options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume));
+ if (qq.isPromise(onResumeRetVal)) {
+ log("Waiting for onResume promise to be fulfilled for " + id);
+ onResumeRetVal.then(
+ function() {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ },
+ function() {
+ log("onResume promise fulfilled - failure indicated. Will not resume.")
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ );
+ }
+ else if (onResumeRetVal !== false) {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ }
+ else {
+ log("onResume callback returned false. Will not resume.");
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var firstChunkIndex = 0,
+ persistedChunkInfoForResume;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex);
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ uploadNextChunk(id);
+ }
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+ function expungeItem(id) {
+ var xhr = fileState[id].xhr;
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ }
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id, persistedChunkData,
+ uuid = qq.getUniqueId();
+
+ if (fileOrBlobData instanceof File) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (qq.isBlob(fileOrBlobData.blob)) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ if (resumeEnabled) {
+ persistedChunkData = getPersistedChunkData(id);
+
+ if (persistedChunkData) {
+ uuid = persistedChunkData.uuid;
+ }
+ }
+
+ fileState[id].uuid = uuid;
+
+ return id;
+ },
+ getName: function(id){
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ expunge: function(id) {
+ return expungeItem(id);
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry) {
+ var name = this.getName(id);
+
+ if (this.isValid(id)) {
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ }
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, this.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeItem(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeItem(id);
+ return true;
+ }
+
+ return false;
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
diff --git a/ajax/libs/file-uploader/3.6.0/fineuploader.min.css b/ajax/libs/file-uploader/3.6.0/fineuploader.min.css
new file mode 100644
index 000000000..110e8fa52
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.0/fineuploader.min.css
@@ -0,0 +1 @@
+.qq-uploader{position:relative;width:100%;}.qq-upload-button{display:block;width:105px;padding:7px 0;text-align:center;background:#800;border-bottom:1px solid #DDD;color:#FFF;}.qq-upload-button-hover{background:#C00;}.qq-upload-button-focus{outline:1px dotted #000;}.qq-upload-drop-area,.qq-upload-extra-drop-area{position:absolute;top:0;left:0;width:100%;height:100%;min-height:30px;z-index:2;background:#FF9797;text-align:center;}.qq-upload-drop-area span{display:block;position:absolute;top:50%;width:100%;margin-top:-8px;font-size:16px;}.qq-upload-extra-drop-area{position:relative;margin-top:50px;font-size:16px;padding-top:30px;height:20px;min-height:40px;}.qq-upload-drop-area-active{background:#FF7171;}.qq-upload-list{margin:0;padding:0;list-style:none;}.qq-upload-list li{margin:0;padding:9px;line-height:15px;font-size:16px;background-color:#FFF0BD;}.qq-upload-file,.qq-upload-spinner,.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-failed-text,.qq-upload-finished,.qq-upload-delete{margin-right:12px;}.qq-upload-spinner{display:inline-block;background:url("loading.gif");width:15px;height:15px;vertical-align:text-bottom;}.qq-drop-processing{display:none;}.qq-drop-processing-spinner{display:inline-block;background:url("processing.gif");width:24px;height:24px;vertical-align:text-bottom;}.qq-upload-finished{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-retry,.qq-upload-delete{display:none;color:#000;}.qq-upload-cancel,.qq-upload-delete{color:#000;}.qq-upload-retryable .qq-upload-retry{display:inline;}.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-delete{font-size:12px;font-weight:normal;}.qq-upload-failed-text{display:none;font-style:italic;font-weight:bold;}.qq-upload-failed-icon{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-fail .qq-upload-failed-text{display:inline;}.qq-upload-retrying .qq-upload-failed-text{display:inline;color:#D60000;}.qq-upload-list li.qq-upload-success{background-color:#5DA30C;color:#FFF;}.qq-upload-list li.qq-upload-fail{background-color:#D60000;color:#FFF;}.qq-progress-bar{background:-moz-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,rgba(30,87,153,1)),color-stop(50%,rgba(41,137,216,1)),color-stop(51%,rgba(32,124,202,1)),color-stop(100%,rgba(125,185,232,1)));background:-webkit-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-o-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-ms-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:linear-gradient(to bottom,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);width:0;height:15px;border-radius:6px;margin-bottom:3px;display:none;}
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.0/fineuploader.min.js b/ajax/libs/file-uploader/3.6.0/fineuploader.min.js
new file mode 100644
index 000000000..9f614b47a
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.0/fineuploader.min.js
@@ -0,0 +1,15 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: -unstable-
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!=null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable!==null&&variable&&typeof variable==="object"&&variable.constructor===Object};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isArray=function(variable){"use strict";return Object.prototype.toString.call(variable)==="[object Array]"};qq.isString=function(maybeString){"use strict";return Object.prototype.toString.call(maybeString)==="[object String]"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFileOrInput=function(maybeFileOrInput){"use strict";if(window.File&&maybeFileOrInput instanceof File){return true}return qq.isInput(maybeFileOrInput)};qq.isInput=function(maybeInput){if(window.HTMLInputElement){if(maybeInput instanceof HTMLInputElement){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}else if(maybeInput.tagName){if(maybeInput.tagName.toLowerCase()==="input"){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&Object.prototype.toString.call(maybeBlob)==="[object Blob]"};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice!==undefined||File.prototype.webkitSlice!==undefined||File.prototype.mozSlice!==undefined)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}});return first};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{defaultResponseError:"Upload failure reason unknown",sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"misc_data",paramNames:{name:"qqblobname"}},paste:{targetElement:null,defaultName:"pasted_image"},camera:{ios:false}};qq.extend(this._options,o,true);this._handleCameraAccess();this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData=this._createUploadDataTracker();this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}if(this._options.paste.targetElement){this._pasteHandler=this._createPasteHandler()}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader "+qq.version+"] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader "+qq.version+"] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},getNetUploads:function(){return this._netUploaded},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._netUploadedOrQueued++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset();this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData.reset();if(this._pasteHandler){this._pasteHandler.reset()}},addFiles:function(filesOrInputs,params,endpoint){var self=this,verifiedFilesOrInputs=[],fileOrInputIndex,fileOrInput,fileIndex;if(filesOrInputs){if(!window.FileList||!(filesOrInputs instanceof FileList)){filesOrInputs=[].concat(filesOrInputs)}for(fileOrInputIndex=0;fileOrInputIndex=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||qq.supportedFeatures.deleteFileCors)},_onSubmitDelete:function(id,onSuccessCallback){if(this._isDeletePossible()){return this._handleCheckedCallback({name:"onSubmitDelete",callback:qq.bind(this._options.callbacks.onSubmitDelete,this,id),onSuccess:onSuccessCallback||qq.bind(this._deleteHandler.sendDelete,this,id,this.getUuid(id)),identifier:id})}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(id){this._uploadData.setStatus(id,qq.status.DELETING)},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);if(isError){this._uploadData.setStatus(id,qq.status.DELETE_FAILED);this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status,xhr)
+}else{this._uploadData.setStatus(id,qq.status.DELETED);this._netUploadedOrQueued--;this._netUploaded--;this._handler.expunge(id);this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){this._uploadData.setStatus(id,qq.status.UPLOADING)},_onInputChange:function(input){if(qq.supportedFeatures.ajaxUploading){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0&&this._netUploadedOrQueued+1>itemLimit){this._itemError("retryFailTooManyItems","");return false}this.log("Retrying upload for '"+fileName+"' (id: "+id+")...");this._filesInProgress.push(id);return true}else{this.log("'"+id+"' is not a valid file ID","error");return false}},_maybeParseAndSendUploadError:function(id,name,response,xhr){if(!response.success){if(xhr&&xhr.status!==200&&!response.error){this._options.callbacks.onError(id,name,"XHR returned response code "+xhr.status,xhr)}else{var errorReason=response.error?response.error:this._options.text.defaultResponseError;this._options.callbacks.onError(id,name,errorReason,xhr)}}},_prepareItemsForUpload:function(items,params,endpoint){var validationDescriptors=this._getValidationDescriptors(items);this._handleCheckedCallback({name:"onValidateBatch",callback:qq.bind(this._options.callbacks.onValidateBatch,this,validationDescriptors),onSuccess:qq.bind(this._onValidateBatchCallbackSuccess,this,validationDescriptors,items,params,endpoint),identifier:"batch validation"})},_upload:function(blobOrFileContainer,params,endpoint){var id=this._handler.add(blobOrFileContainer),name=this._handler.getName(id);this._uploadData.added(id);if(params){this.setParams(params,id)}if(endpoint){this.setEndpoint(endpoint,id)}this._handleCheckedCallback({name:"onSubmit",callback:qq.bind(this._options.callbacks.onSubmit,this,id,name),onSuccess:qq.bind(this._onSubmitCallbackSuccess,this,id,name),onFailure:qq.bind(this._fileOrBlobRejected,this,id,name),identifier:id})},_onSubmitCallbackSuccess:function(id,name){this._uploadData.setStatus(id,qq.status.SUBMITTED);this._onSubmit(id,name);this._options.callbacks.onSubmitted(id,name);if(this._options.autoUpload){if(!this._handler.upload(id)){this._uploadData.setStatus(id,qq.status.QUEUED)}}else{this._storeForLater(id)}},_storeForLater:function(id){this._storedIds.push(id)},_onValidateBatchCallbackSuccess:function(validationDescriptors,items,params,endpoint){var errorMessage,itemLimit=this._options.validation.itemLimit,proposedNetFilesUploadedOrQueued=this._netUploadedOrQueued+validationDescriptors.length;if(itemLimit===0||proposedNetFilesUploadedOrQueued<=itemLimit){if(items.length>0){this._handleCheckedCallback({name:"onValidate",callback:qq.bind(this._options.callbacks.onValidate,this,items[0]),onSuccess:qq.bind(this._onValidateCallbackSuccess,this,items,0,params,endpoint),onFailure:qq.bind(this._onValidateCallbackFailure,this,items,0,params,endpoint),identifier:"Item '"+items[0].name+"', size: "+items[0].size})}else{this._itemError("noFilesError","")}}else{errorMessage=this._options.messages.tooManyItemsError.replace(/\{netItems\}/g,proposedNetFilesUploadedOrQueued).replace(/\{itemLimit\}/g,itemLimit);this._batchError(errorMessage)}},_onValidateCallbackSuccess:function(items,index,params,endpoint){var nextIndex=index+1,validationDescriptor=this._getValidationDescriptor(items[index]),validItem=false;if(this._validateFileOrBlobData(items[index],validationDescriptor)){validItem=true;this._upload(items[index],params,endpoint)}this._maybeProcessNextItemAfterOnValidateCallback(validItem,items,nextIndex,params,endpoint)},_onValidateCallbackFailure:function(items,index,params,endpoint){var nextIndex=index+1;this._fileOrBlobRejected(undefined,items[0].name);this._maybeProcessNextItemAfterOnValidateCallback(false,items,nextIndex,params,endpoint)},_maybeProcessNextItemAfterOnValidateCallback:function(validItem,items,index,params,endpoint){var self=this;if(items.length>index){if(validItem||!this._options.validation.stopOnFirstInvalidFile){setTimeout(function(){var validationDescriptor=self._getValidationDescriptor(items[index]);self._handleCheckedCallback({name:"onValidate",callback:qq.bind(self._options.callbacks.onValidate,self,items[index]),onSuccess:qq.bind(self._onValidateCallbackSuccess,self,items,index,params,endpoint),onFailure:qq.bind(self._onValidateCallbackFailure,self,items,index,params,endpoint),identifier:"Item '"+validationDescriptor.name+"', size: "+validationDescriptor.size})},0)}}},_validateFileOrBlobData:function(item,validationDescriptor){var name=validationDescriptor.name,size=validationDescriptor.size,valid=true;if(this._options.callbacks.onValidate(validationDescriptor)===false){valid=false}if(qq.isFileOrInput(item)&&!this._isAllowedExtension(name)){this._itemError("typeError",name);valid=false}else if(size===0){this._itemError("emptyError",name);valid=false}else if(size&&this._options.validation.sizeLimit&&size>this._options.validation.sizeLimit){this._itemError("sizeError",name);valid=false}else if(size&&size999);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size!==undefined){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}},_handleCameraAccess:function(){if(this._options.camera.ios&&qq.ios()){this._options.multiple=false;if(this._options.validation.acceptFiles===null){this._options.validation.acceptFiles="image/*;capture=camera"}else{this._options.validation.acceptFiles+=",image/*;capture=camera"}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,droppedFiles=[],disposeSupport=new qq.DisposeSupport;options={dropZoneElements:[],hideDropZonesBeforeEnter:false,allowMultipleItems:true,classes:{dropActive:null},callbacks:new qq.DragAndDrop.callbacks};qq.extend(options,o,true);setupDragDrop();function uploadDroppedFiles(files){options.callbacks.dropLog("Grabbed "+files.length+" dropped files.");dz.dropDisabled(false);options.callbacks.processingDroppedFilesComplete(files)}function traverseFileTree(entry){var dirReader,i,parseEntryPromise=new qq.Promise;if(entry.isFile){entry.file(function(file){droppedFiles.push(file);parseEntryPromise.success()},function(fileError){options.callbacks.dropLog("Problem parsing '"+entry.fullPath+"'. FileError code "+fileError.code+".","error");parseEntryPromise.failure()})}else if(entry.isDirectory){dirReader=entry.createReader();dirReader.readEntries(function(entries){var entriesLeft=entries.length;for(i=0;i1&&!options.allowMultipleItems){options.callbacks.processingDroppedFilesComplete([]);options.callbacks.dropError("tooManyFilesError","");dz.dropDisabled(false);handleDataTransferPromise.failure()}else{droppedFiles=[];if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file",unsupportedBrowser:"Unrecoverable error - this browser does not permit file uploading of any kind."},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false,prependFiles:false},paste:{promptForName:false,namePromptMessage:"Please name this image"},showMessage:function(message){setTimeout(function(){window.alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=window.confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)},showPrompt:function(message,defaultValue){var promise=new qq.Promise,retVal=window.prompt(message,defaultValue);if(retVal!=null&&qq.trimStr(retVal).length>0){promise.success(retVal)}else{promise.failure("Undefined or invalid user-supplied value.")}return promise}},true);qq.extend(this._options,o,true);if(!qq.supportedFeatures.uploading||this._options.cors.expected&&!qq.supportedFeatures.uploadCors){this._options.element.innerHTML=""+this._options.messages.unsupportedBrowser+"
"}else{this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop();if(this._options.paste.targetElement&&this._options.paste.promptForName){this._setupPastePrompt()}this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0}};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop();this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dropZoneElements=this._options.dragAndDrop.extraDropzones,preventSelectFiles;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){dropZoneElements.push(this._find(this._options.element,"drop"))}return new qq.DragAndDrop({dropZoneElements:dropZoneElements,hideDropZonesBeforeEnter:this._options.dragAndDrop.hideDropzones,allowMultipleItems:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{processingDroppedFiles:function(){var input=self._button.getInput();qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)},processingDroppedFilesComplete:function(files){var input=self._button.getInput();qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles);if(files){self.addFiles(files)}},dropError:function(code,errorData){self._itemError(code,errorData)},dropLog:function(message,level){self.log(message,level)}}})},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){var item=this.getItemByFileId(id);if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}else{qq(item).addClass(this._classes.retryable);return false}},_onSubmitDelete:function(id){var onSuccessCallback=qq.bind(this._onSubmitDeleteSuccess,this,id);qq.FineUploaderBasic.prototype._onSubmitDelete.call(this,id,onSuccessCallback)},_onSubmitDeleteSuccess:function(id){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}if(this._options.display.prependFiles){this._prependItem(item)}else{this._listElement.appendChild(item)}this._filesInBatchAddedToUi+=1;if(this._options.display.fileSizeOnSubmit&&qq.supportedFeatures.ajaxUploading){this._displayFileSize(id)}},_prependItem:function(item){var parentEl=this._listElement,beforeEl=parentEl.firstChild;if(this._totalFilesInBatch>1&&this._filesInBatchAddedToUi>0){beforeEl=qq(parentEl).children()[this._filesInBatchAddedToUi-1].nextSibling}parentEl.insertBefore(item,beforeEl)},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=item.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")
+}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_itemError:function(code,name){var message=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(message)},_batchError:function(message){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments);this._options.showMessage(message)},_setupPastePrompt:function(){var self=this;this._options.callbacks.onPasteReceived=function(){var message=self._options.paste.namePromptMessage,defaultVal=self._options.paste.defaultName;return self._options.showPrompt(message,defaultVal)}},_fileOrBlobRejected:function(id,name){this._totalFilesInBatch-=1;qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this,arguments)},_prepareItemsForUpload:function(items,params,endpoint){this._totalFilesInBatch=items.length;this._filesInBatchAddedToUi=0;qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this,arguments)}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,handlerImpl,api;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){},onUuidChanged:function(id,newUuid){}};qq.extend(options,o);log=options.log;function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){var cancelRetVal=handlerImpl.cancel(id);if(qq.isPromise(cancelRetVal)){cancelRetVal.then(function(){cancelSuccess(id)})}else if(cancelRetVal!==false){cancelSuccess(id)}},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},reset:function(){log("Resetting upload handler");api.cancelAll();queue=[];handlerImpl.reset()},expunge:function(id){return handlerImpl.expunge(id)},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}};return api};qq.UploadHandlerForm=function(o,uploadCompleteCallback,onUuidChange,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},formHandlerInstanceId=qq.getUniqueId(),api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var iframeName=iframe.id,fileId=getFileIdForIframeName(iframeName);onloadCallbacks[uuids[fileId]]=callback;detachLoadEvents[fileId]=qq(iframe).attach("load",function(){if(inputs[fileId]){log("Received iframe load event for CORS upload request (iframe name "+iframeName+")");postMessageCallbackTimers[iframeName]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for iframe name "+iframeName;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(iframeName,function(message){log("Received the following window message: '"+message+"'");var response=parseResponse(getFileIdForIframeName(iframeName),message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){log("Handling response for iframe name "+iframeName);clearTimeout(postMessageCallbackTimers[iframeName]);delete postMessageCallbackTimers[iframeName];detachLoadEvent(iframeName);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(iframeName);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(id,iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHtml=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHtml);if(innerHtml&&innerHtml.match(/^ ');iframe.setAttribute("id",iframeName);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}function expungeFile(id){delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(getIframeName(id));if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}}function getFileIdForIframeName(iframeName){return iframeName.split("_")[0]}function getIframeName(fileId){return fileId+"_"+formHandlerInstanceId}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){if(api.isValid(id)){return inputs[id].value.replace(/.*(\/|\\)/,"")}else{log(id+" is not a valid item ID.","error")}},isValid:function(id){return inputs[id]!==undefined},reset:function(){inputs=[];uuids=[];detachLoadEvents={};formHandlerInstanceId=qq.getUniqueId()},expunge:function(id){return expungeFile(id)},getUuid:function(id){return uuids[id]},cancel:function(id){var onCancelRetVal=options.onCancel(id,api.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeFile(id)})}else if(onCancelRetVal!==false){expungeFile(id);return true}return false},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,api.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(id,iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove()}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,onUuidChange,logCallback){"use strict";var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.supportedFeatures.chunking,resumeEnabled=options.resume.enabled&&chunkFiles&&qq.supportedFeatures.resume,resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);if(fileState[id]){delete fileState[id].xhr}uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(id,xhr){var response;try{response=qq.parseJson(xhr.responseText);if(response.newUuid!==undefined){log("Server requested UUID change from '"+fileState[id].uuid+"' to '"+response.newUuid+"'");fileState[id].uuid=response.newUuid;onUuidChanged(id,response.newUuid)}}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(id,xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex){var currentChunkIndex;for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}uploadNextChunk(id)}function onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex);calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}function handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex){var name=api.getName(id),firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part),onResumeRetVal;onResumeRetVal=options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume));if(qq.isPromise(onResumeRetVal)){log("Waiting for onResume promise to be fulfilled for "+id);onResumeRetVal.then(function(){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)},function(){log("onResume promise fulfilled - failure indicated. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)})}else if(onResumeRetVal!==false){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)}else{log("onResume callback returned false. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}function handleFileChunkingUpload(id,retry){var firstChunkIndex=0,persistedChunkInfoForResume;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex)}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{uploadNextChunk(id)}}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}function expungeItem(id){var xhr=fileState[id].xhr;if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]}api={add:function(fileOrBlobData){var id,persistedChunkData,uuid=qq.getUniqueId();if(fileOrBlobData instanceof File){id=fileState.push({file:fileOrBlobData})-1}else if(qq.isBlob(fileOrBlobData.blob)){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}if(resumeEnabled){persistedChunkData=getPersistedChunkData(id);if(persistedChunkData){uuid=persistedChunkData.uuid}}fileState[id].uuid=uuid;return id},getName:function(id){if(api.isValid(id)){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}}else{log(id+" is not a valid item ID.","error")}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},expunge:function(id){return expungeItem(id)},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);if(this.isValid(id)){options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}}},cancel:function(id){var onCancelRetVal=options.onCancel(id,this.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeItem(id)})}else if(onCancelRetVal!==false){expungeItem(id);return true}return false},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.0/iframe.xss.response.js b/ajax/libs/file-uploader/3.6.0/iframe.xss.response.js
new file mode 100644
index 000000000..e11fca1e5
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.0/iframe.xss.response.js
@@ -0,0 +1,6 @@
+(function() {
+ var match = /(\{.+\}).+/.exec(document.body.innerHTML);
+ if (match) {
+ parent.postMessage(match[1], '*');
+ }
+}());
diff --git a/ajax/libs/file-uploader/3.6.0/iframe.xss.response.min.js b/ajax/libs/file-uploader/3.6.0/iframe.xss.response.min.js
new file mode 100644
index 000000000..345cf3603
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.0/iframe.xss.response.min.js
@@ -0,0 +1 @@
+!function(){var match=/(\{.+\}).+/.exec(document.body.innerHTML);if(match){parent.postMessage(match[1],"*")}}();
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.0/loading.gif b/ajax/libs/file-uploader/3.6.0/loading.gif
new file mode 100644
index 000000000..6fba77609
Binary files /dev/null and b/ajax/libs/file-uploader/3.6.0/loading.gif differ
diff --git a/ajax/libs/file-uploader/3.6.0/processing.gif b/ajax/libs/file-uploader/3.6.0/processing.gif
new file mode 100644
index 000000000..7c99504e1
Binary files /dev/null and b/ajax/libs/file-uploader/3.6.0/processing.gif differ
diff --git a/ajax/libs/file-uploader/3.6.1/fineuploader-jquery.js b/ajax/libs/file-uploader/3.6.1/fineuploader-jquery.js
new file mode 100644
index 000000000..b46908467
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.1/fineuploader-jquery.js
@@ -0,0 +1,5039 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: -unstable-
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity != null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return Object.prototype.toString.call(variable) === '[object Object]';
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isArray = function(variable) {
+ "use strict";
+ return Object.prototype.toString.call(variable) === "[object Array]";
+}
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFile = function(maybeFile) {
+ "use strict";
+
+ return window.File && Object.prototype.toString.call(maybeFile) === '[object File]'
+};
+
+qq.isFileList = function(maybeFileList) {
+ return window.FileList && Object.prototype.toString.call(maybeFileList) === '[object FileList]'
+}
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+
+ return qq.isFile(maybeFileOrInput) || qq.isInput(maybeFileOrInput);
+};
+
+qq.isInput = function(maybeInput) {
+ if (window.HTMLInputElement) {
+ if (Object.prototype.toString.call(maybeInput) === '[object HTMLInputElement]') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ else if (maybeInput.tagName) {
+ if (maybeInput.tagName.toLowerCase() === 'input') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && Object.prototype.toString.call(maybeBlob) === '[object Blob]';
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice !== undefined || File.prototype.webkitSlice !== undefined || File.prototype.mozSlice !== undefined);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+
+ return first;
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+qq.ios = function() {
+ "use strict";
+ return navigator.userAgent.indexOf("iPad") !== -1
+ || navigator.userAgent.indexOf("iPod") !== -1
+ || navigator.userAgent.indexOf("iPhone") !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(objOrArray, callback) {
+ "use strict";
+ var keyOrIndex, retVal;
+ if (objOrArray) {
+ if (qq.isArray(objOrArray)) {
+ for (keyOrIndex = 0; keyOrIndex < objOrArray.length; keyOrIndex++) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ else {
+ for (keyOrIndex in objOrArray) {
+ if (Object.prototype.hasOwnProperty.call(objOrArray, keyOrIndex)) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+ }
+};
+
+//include any args that should be passed to the new function after the context arg
+qq.bind = function(oldFunc, context) {
+ if (qq.isFunction(oldFunc)) {
+ var args = Array.prototype.slice.call(arguments, 2);
+
+ return function() {
+ if (arguments.length) {
+ args = args.concat(Array.prototype.slice.call(arguments))
+ }
+ return oldFunc.apply(context, args);
+ };
+ }
+
+ throw new Error("first parameter must be a function!");
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ cookie;
+
+ qq.each(ca, function(idx, part) {
+ var cookiePart = part;
+ while (cookiePart.charAt(0)==' ') {
+ cookiePart = cookiePart.substring(1, cookiePart.length);
+ }
+
+ if (cookiePart.indexOf(nameEQ) === 0) {
+ cookie = cookiePart.substring(nameEQ.length, cookiePart.length);
+ return false;
+ }
+ });
+
+ return cookie;
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+qq.version="-unstable-";qq.supportedFeatures = (function() {
+ var supportsUploading,
+ supportsAjaxFileUploading,
+ supportsFolderDrop,
+ supportsChunking,
+ supportsResume,
+ supportsUploadViaPaste,
+ supportsUploadCors,
+ supportsDeleteFileCors;
+
+
+ function testSupportsFileInputElement() {
+ var supported = true,
+ tempInput;
+
+ try {
+ tempInput = document.createElement('input');
+ tempInput.type = 'file';
+ qq(tempInput).hide();
+
+ if(tempInput.disabled) {
+ supported = false;
+ }
+ }
+ catch(ex) {
+ supported = false;
+ }
+
+ return supported;
+ }
+
+ //only way to test for Filesystem API support since webkit does not expose the DataTransfer interface
+ function isChrome21OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[2][1-9]|Chrome\/[3-9][0-9]/) !== undefined;
+ }
+
+ //only way to test for complete Clipboard API support at this time
+ function isChrome14OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/) !== undefined;
+ }
+
+
+ supportsUploading = testSupportsFileInputElement();
+
+ supportsAjaxFileUploading = supportsUploading && qq.isXhrUploadSupported();
+
+ supportsFolderDrop = supportsAjaxFileUploading && isChrome21OrHigher();
+
+ supportsChunking = supportsAjaxFileUploading && qq.isFileChunkingSupported();
+
+ supportsResume = supportsAjaxFileUploading && supportsChunking && qq.areCookiesEnabled();
+
+ supportsUploadViaPaste = supportsAjaxFileUploading && isChrome14OrHigher();
+
+ supportsUploadCors = supportsUploading && (window.postMessage !== undefined || supportsAjaxFileUploading);
+
+ supportsDeleteFileCors = supportsAjaxFileUploading;
+
+
+ return {
+ uploading: supportsUploading,
+ ajaxUploading: supportsAjaxFileUploading,
+ fileDrop: supportsAjaxFileUploading, //NOTE: will also return true for touch-only devices. It's not currently possible to accurately test for touch-only devices
+ folderDrop: supportsFolderDrop,
+ chunking: supportsChunking,
+ resume: supportsResume,
+ uploadCustomHeaders: supportsAjaxFileUploading,
+ uploadNonMultipart: supportsAjaxFileUploading,
+ itemSizeValidation: supportsAjaxFileUploading,
+ uploadViaPaste: supportsUploadViaPaste,
+ progressBar: supportsAjaxFileUploading,
+ uploadCors: supportsUploadCors,
+ deleteFileCors: supportsDeleteFileCors,
+ canDetermineSize: supportsAjaxFileUploading
+ }
+
+}());
+/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallbacks = [],
+ failureCallbacks = [],
+ doneCallbacks = [],
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ if (onSuccess) {
+ successCallbacks.push(onSuccess);
+ }
+ if (onFailure) {
+ failureCallbacks.push(onFailure);
+ }
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+
+ return this;
+ },
+
+ done: function(callback) {
+ if (state === 0) {
+ doneCallbacks.push(callback);
+ }
+ else {
+ callback();
+ }
+
+ return this;
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallbacks.length) {
+ qq.each(successCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallbacks.length) {
+ qq.each(failureCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ }
+ };
+};
+
+qq.isPromise = function(maybePromise) {
+ return maybePromise && maybePromise.then && maybePromise.done;
+};/*globals qq*/
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ disposeSupport = new qq.DisposeSupport(),
+ options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input) {},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+
+ qq.extend(options, o);
+
+ // make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};qq.UploadData = function(uploaderProxy) {
+ var data = [],
+ byId = {},
+ byUuid = {},
+ byStatus = {},
+ api;
+
+ function getDataByIds(ids) {
+ if (qq.isArray(ids)) {
+ var entries = [];
+
+ qq.each(ids, function(idx, id) {
+ entries.push(data[byId[id]]);
+ });
+
+ return entries;
+ }
+
+ return data[byId[ids]];
+ }
+
+ function getDataByUuids(uuids) {
+ if (qq.isArray(uuids)) {
+ var entries = [];
+
+ qq.each(uuids, function(idx, uuid) {
+ entries.push(data[byUuid[uuid]]);
+ });
+
+ return entries;
+ }
+
+ return data[byUuid[uuids]];
+ }
+
+ function getDataByStatus(status) {
+ var statusResults = [],
+ statuses = [].concat(status);
+
+ qq.each(statuses, function(index, statusEnum) {
+ var statusResultIndexes = byStatus[statusEnum];
+
+ if (statusResultIndexes !== undefined) {
+ qq.each(statusResultIndexes, function(i, dataIndex) {
+ statusResults.push(data[dataIndex]);
+ });
+ }
+ });
+
+ return statusResults;
+ }
+
+ api = {
+ added: function(id) {
+ var uuid = uploaderProxy.getUuid(id),
+ name = uploaderProxy.getName(id),
+ size = uploaderProxy.getSize(id),
+ status = qq.status.SUBMITTING;
+
+ var index = data.push({
+ id: id,
+ name: name,
+ uuid: uuid,
+ size: size,
+ status: status
+ }) - 1;
+
+ byId[id] = index;
+
+ byUuid[uuid] = index;
+
+ if (byStatus[status] === undefined) {
+ byStatus[status] = [];
+ }
+ byStatus[status].push(index);
+
+ uploaderProxy.onStatusChange(id, undefined, status);
+ },
+
+ retrieve: function(optionalFilter) {
+ if (qq.isObject(optionalFilter) && data.length) {
+ if (optionalFilter.id !== undefined) {
+ return getDataByIds(optionalFilter.id);
+ }
+
+ else if (optionalFilter.uuid !== undefined) {
+ return getDataByUuids(optionalFilter.uuid);
+ }
+
+ else if (optionalFilter.status) {
+ return getDataByStatus(optionalFilter.status);
+ }
+ }
+ else {
+ return qq.extend([], data, true);
+ }
+ },
+
+ reset: function() {
+ data = [];
+ byId = {};
+ byUuid = {};
+ byStatus = {};
+ },
+
+ setStatus: function(id, newStatus) {
+ var dataIndex = byId[id],
+ oldStatus = data[dataIndex].status,
+ byStatusOldStatusIndex = qq.indexOf(byStatus[oldStatus], dataIndex);
+
+ byStatus[oldStatus].splice(byStatusOldStatusIndex, 1);
+
+ data[dataIndex].status = newStatus;
+
+ if (byStatus[newStatus] === undefined) {
+ byStatus[newStatus] = [];
+ }
+ byStatus[newStatus].push(dataIndex);
+
+ uploaderProxy.onStatusChange(id, oldStatus, newStatus);
+ },
+
+ uuidChanged: function(id, newUuid) {
+ var dataIndex = byId[id],
+ oldUuid = data[dataIndex].uuid;
+
+ data[dataIndex].uuid = newUuid;
+ byUuid[newUuid] = dataIndex;
+ delete byUuid[oldUuid];
+ }
+ };
+
+ return api;
+};
+
+qq.status = {
+ SUBMITTING: "submitting",
+ SUBMITTED: "submitted",
+ REJECTED: "rejected",
+ QUEUED: "queued",
+ CANCELED: "canceled",
+ UPLOADING: "uploading",
+ UPLOAD_RETRYING: "retrying upload",
+ UPLOAD_SUCCESSFUL: "upload successful",
+ UPLOAD_FAILED: "upload failed",
+ DELETE_FAILED: "delete failed",
+ DELETING: "deleting",
+ DELETED: "deleted"
+};qq.FineUploaderBasic = function(o) {
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true,
+ acceptFiles: null
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON, maybeXhr){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){},
+ onPasteReceived: function(blob) {},
+ onStatusChange: function(id, oldStatus, newStatus) {}
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ defaultResponseError: "Upload failure reason unknown",
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'misc_data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ },
+ camera: {
+ ios: false
+ }
+ };
+
+ qq.extend(this._options, o, true);
+
+ this._handleCameraAccess();
+
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData = this._createUploadDataTracker();
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader ' + qq.version + '] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader ' + qq.version + '] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function() {
+ return this._filesInProgress.length;
+ },
+ getNetUploads: function() {
+ return this._netUploaded;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netUploadedOrQueued++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData.reset();
+
+ if (this._pasteHandler) {
+ this._pasteHandler.reset();
+ }
+ },
+ addFiles: function(filesOrInputs, params, endpoint) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ fileOrInputIndex, fileOrInput, fileIndex;
+
+ if (filesOrInputs) {
+ if (!qq.isFileList(filesOrInputs)) {
+ filesOrInputs = [].concat(filesOrInputs);
+ }
+
+ for (fileOrInputIndex = 0; fileOrInputIndex < filesOrInputs.length; fileOrInputIndex+=1) {
+ fileOrInput = filesOrInputs[fileOrInputIndex];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ if (qq.isInput(fileOrInput) && qq.supportedFeatures.ajaxUploading) {
+ for (fileIndex = 0; fileIndex < fileOrInput.files.length; fileIndex++) {
+ verifiedFilesOrInputs.push(fileOrInput.files[fileIndex]);
+ }
+ }
+ else {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Received ' + verifiedFilesOrInputs.length + ' files or inputs.');
+ this._prepareItemsForUpload(verifiedFilesOrInputs, params, endpoint);
+ }
+ },
+ addBlobs: function(blobDataOrArray, params, endpoint) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._prepareItemsForUpload(verifiedBlobDataList, params, endpoint);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ doesExist: function(fileOrBlobId) {
+ return this._handler.isValid(fileOrBlobId);
+ },
+ getUploads: function(optionalFilter) {
+ return this._uploadData.retrieve(optionalFilter);
+ },
+ _handleCheckedCallback: function(details) {
+ var self = this,
+ callbackRetVal = details.callback();
+
+ if (qq.isPromise(callbackRetVal)) {
+ this.log(details.name + " - waiting for " + details.name + " promise to be fulfilled for " + details.identifier);
+ return callbackRetVal.then(
+ function(successParam) {
+ self.log(details.name + " promise success for " + details.identifier);
+ details.onSuccess(successParam);
+ },
+ function() {
+ if (details.onFailure) {
+ self.log(details.name + " promise failure for " + details.identifier);
+ details.onFailure();
+ }
+ else {
+ self.log(details.name + " promise failure for " + details.identifier);
+ }
+ });
+ }
+
+ if (callbackRetVal !== false) {
+ details.onSuccess(callbackRetVal);
+ }
+ else {
+ if (details.onFailure) {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Invoking failure callback.")
+ details.onFailure();
+ }
+ else {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Will not proceed.")
+ }
+ }
+
+ return callbackRetVal;
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.supportedFeatures.ajaxUploading,
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result, xhr);
+ },
+ onCancel: function(id, name) {
+ return self._handleCheckedCallback({
+ name: "onCancel",
+ callback: qq.bind(self._options.callbacks.onCancel, self, id, name),
+ onSuccess: qq.bind(self._onCancel, self, id, name),
+ identifier: id
+ });
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ onUuidChanged: function(id, newUuid) {
+ self._uploadData.uuidChanged(id, newUuid);
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ self._handleCheckedCallback({
+ name: "onPasteReceived",
+ callback: qq.bind(self._options.callbacks.onPasteReceived, self, blob),
+ onSuccess: qq.bind(self._handlePasteSuccess, self, blob),
+ identifier: "pasted image"
+ });
+ }
+ }
+ });
+ },
+ _createUploadDataTracker: function() {
+ var self = this;
+
+ return new qq.UploadData({
+ getName: function(id) {
+ return self.getName(id);
+ },
+ getUuid: function(id) {
+ return self.getUuid(id);
+ },
+ getSize: function(id) {
+ return self.getSize(id);
+ },
+ onStatusChange: function(id, oldStatus, newStatus) {
+ self._options.callbacks.onStatusChange(id, oldStatus, newStatus);
+ }
+ });
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total) {
+ //nothing to do yet in core uploader
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_FAILED);
+ this._netUploadedOrQueued--;
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_SUCCESSFUL);
+ this._netUploaded++;
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.CANCELED);
+
+ this._netUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected || qq.supportedFeatures.deleteFileCors));
+ },
+ _onSubmitDelete: function(id, onSuccessCallback) {
+ if (this._isDeletePossible()) {
+ return this._handleCheckedCallback({
+ name: "onSubmitDelete",
+ callback: qq.bind(this._options.callbacks.onSubmitDelete, this, id),
+ onSuccess: onSuccessCallback || qq.bind(this._deleteHandler.sendDelete, this, id, this.getUuid(id)),
+ identifier: id
+ });
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(id) {
+ this._uploadData.setStatus(id, qq.status.DELETING);
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this._uploadData.setStatus(id, qq.status.DELETE_FAILED);
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.DELETED);
+ this._netUploadedOrQueued--;
+ this._netUploaded--;
+ this._handler.expunge(id);
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.UPLOADING);
+ },
+ _onInputChange: function(input){
+ if (qq.supportedFeatures.ajaxUploading) {
+ this.addFiles(input.files);
+ }
+ else {
+ this.addFiles(input);
+ }
+
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems", "");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : this._options.text.defaultResponseError;
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ var validationDescriptors = this._getValidationDescriptors(items);
+
+ this._handleCheckedCallback({
+ name: "onValidateBatch",
+ callback: qq.bind(this._options.callbacks.onValidateBatch, this, validationDescriptors),
+ onSuccess: qq.bind(this._onValidateBatchCallbackSuccess, this, validationDescriptors, items, params, endpoint),
+ identifier: "batch validation"
+ });
+ },
+ _upload: function(blobOrFileContainer, params, endpoint) {
+ var id = this._handler.add(blobOrFileContainer),
+ name = this._handler.getName(id);
+
+ this._uploadData.added(id);
+
+ if (params) {
+ this.setParams(params, id);
+ }
+
+ if (endpoint) {
+ this.setEndpoint(endpoint, id);
+ }
+
+ this._handleCheckedCallback({
+ name: "onSubmit",
+ callback: qq.bind(this._options.callbacks.onSubmit, this, id, name),
+ onSuccess: qq.bind(this._onSubmitCallbackSuccess, this, id, name),
+ onFailure: qq.bind(this._fileOrBlobRejected, this, id, name),
+ identifier: id
+ });
+ },
+ _onSubmitCallbackSuccess: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.SUBMITTED);
+
+ this._onSubmit(id, name);
+ this._options.callbacks.onSubmitted(id, name);
+
+ if (this._options.autoUpload) {
+ if (!this._handler.upload(id)) {
+ this._uploadData.setStatus(id, qq.status.QUEUED);
+ }
+ }
+ else {
+ this._storeForLater(id);
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _onValidateBatchCallbackSuccess: function(validationDescriptors, items, params, endpoint) {
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netUploadedOrQueued + validationDescriptors.length;
+
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ if (items.length > 0) {
+ this._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(this._options.callbacks.onValidate, this, items[0]),
+ onSuccess: qq.bind(this._onValidateCallbackSuccess, this, items, 0, params, endpoint),
+ onFailure: qq.bind(this._onValidateCallbackFailure, this, items, 0, params, endpoint),
+ identifier: "Item '" + items[0].name + "', size: " + items[0].size
+ });
+ }
+ else {
+ this._itemError("noFilesError", "");
+ }
+ }
+ else {
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ },
+ _onValidateCallbackSuccess: function(items, index, params, endpoint) {
+ var nextIndex = index+1,
+ validationDescriptor = this._getValidationDescriptor(items[index]),
+ validItem = false;
+
+ if (this._validateFileOrBlobData(items[index], validationDescriptor)) {
+ validItem = true;
+ this._upload(items[index], params, endpoint);
+ }
+
+ this._maybeProcessNextItemAfterOnValidateCallback(validItem, items, nextIndex, params, endpoint);
+ },
+ _onValidateCallbackFailure: function(items, index, params, endpoint) {
+ var nextIndex = index+ 1;
+
+ this._fileOrBlobRejected(undefined, items[0].name);
+
+ this._maybeProcessNextItemAfterOnValidateCallback(false, items, nextIndex, params, endpoint);
+ },
+ _maybeProcessNextItemAfterOnValidateCallback: function(validItem, items, index, params, endpoint) {
+ var self = this;
+
+ if (items.length > index) {
+ if (validItem || !this._options.validation.stopOnFirstInvalidFile) {
+ //use setTimeout to prevent a stack overflow with a large number of files in the batch & non-promissory callbacks
+ setTimeout(function() {
+ var validationDescriptor = self._getValidationDescriptor(items[index]);
+
+ self._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(self._options.callbacks.onValidate, self, items[index]),
+ onSuccess: qq.bind(self._onValidateCallbackSuccess, self, items, index, params, endpoint),
+ onFailure: qq.bind(self._onValidateCallbackFailure, self, items, index, params, endpoint),
+ identifier: "Item '" + validationDescriptor.name + "', size: " + validationDescriptor.size
+ });
+ }, 0);
+ }
+ }
+ },
+ _validateFileOrBlobData: function(item, validationDescriptor) {
+ var name = validationDescriptor.name,
+ size = validationDescriptor.size,
+ valid = true;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ valid = false;
+ }
+
+ if (qq.isFileOrInput(item) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ valid = false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ valid = false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ valid = false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ valid = false;
+ }
+
+ if (!valid) {
+ this._fileOrBlobRejected(undefined, name);
+ }
+
+ return valid;
+ },
+ _fileOrBlobRejected: function(id, name) {
+ if (id !== undefined) {
+ this._uploadData.setStatus(id, qq.status.REJECTED);
+ }
+ },
+ _itemError: function(code, nameOrNames) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ names = [].concat(nameOrNames),
+ name = names[0],
+ extensionsForMessage, placeholderMatch;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ placeholderMatch = message.match(/(\{\w+\})/g);
+ if (placeholderMatch !== null) {
+ qq.each(placeholderMatch, function(idx, placeholder) {
+ r(placeholder, names[idx]);
+ });
+ }
+
+ this._options.callbacks.onError(null, name, message, undefined);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message, undefined);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1000;
+ i++;
+ } while (bytes > 999);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size !== undefined) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ },
+ _handleCameraAccess: function() {
+ if (this._options.camera.ios && qq.ios()) {
+ this._options.multiple = false;
+
+ if (this._options.validation.acceptFiles === null) {
+ this._options.validation.acceptFiles = "image/*;capture=camera";
+ }
+ else {
+ this._options.validation.acceptFiles += ",image/*;capture=camera";
+ }
+ }
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz,
+ droppedFiles = [],
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropZoneElements: [],
+ hideDropZonesBeforeEnter: false,
+ allowMultipleItems: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: new qq.DragAndDrop.callbacks()
+ };
+
+ qq.extend(options, o, true);
+
+ setupDragDrop();
+
+ function uploadDroppedFiles(files) {
+ options.callbacks.dropLog('Grabbed ' + files.length + " dropped files.");
+ dz.dropDisabled(false);
+ options.callbacks.processingDroppedFilesComplete(files);
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i,
+ parseEntryPromise = new qq.Promise();
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ droppedFiles.push(file);
+ parseEntryPromise.success();
+ },
+ function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+ else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ var entriesLeft = entries.length;
+
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]).done(function() {
+ entriesLeft-=1;
+
+ if (entriesLeft === 0) {
+ parseEntryPromise.success();
+ }
+ });
+ }
+
+ if (!entries.length) {
+ parseEntryPromise.success();
+ }
+ }, function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+
+ return parseEntryPromise;
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry,
+ pendingFolderPromises = [],
+ handleDataTransferPromise = new qq.Promise();
+
+ options.callbacks.processingDroppedFiles();
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.allowMultipleItems) {
+ options.callbacks.processingDroppedFilesComplete([]);
+ options.callbacks.dropError('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ handleDataTransferPromise.failure();
+ }
+ else {
+ droppedFiles = [];
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ }
+
+ else {
+ pendingFolderPromises.push(traverseFileTree(entry).done(function() {
+ pendingFolderPromises.pop();
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }));
+ }
+ }
+ }
+ }
+ else {
+ droppedFiles = dataTransfer.files;
+ }
+
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }
+
+ return handleDataTransferPromise;
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer).done(function() {
+ uploadDroppedFiles(droppedFiles);
+ });
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ var dropZones = options.dropZoneElements;
+
+ qq.each(dropZones, function(idx, dropZone) {
+ setupDropzone(dropZone);
+ })
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (dropZones.length && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).css({display: 'block'});
+ });
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropZonesBeforeEnter && qq.FineUploader.prototype._leaving_document_out(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setupExtraDropzone: function(element) {
+ options.dropZoneElements.push(element);
+ setupDropzone(element);
+ },
+
+ removeDropzone: function(element) {
+ var i,
+ dzs = options.dropZoneElements;
+
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+qq.DragAndDrop.callbacks = function() {
+ return {
+ processingDroppedFiles: function() {},
+ processingDroppedFilesComplete: function(files) {},
+ dropError: function(code, errorSpecifics) {
+ qq.log("Drag & drop error code '" + code + " with these specifics: '" + errorSpecifics + "'", "error");
+ },
+ dropLog: function(message, level) {
+ qq.log(message, level);
+ }
+ }
+}
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file",
+ unsupportedBrowser: "Unrecoverable error - this browser does not permit file uploading of any kind."
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false,
+ prependFiles: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+
+ if (!qq.supportedFeatures.uploading || (this._options.cors.expected && !qq.supportedFeatures.uploadCors)) {
+ this._options.element.innerHTML = "" + this._options.messages.unsupportedBrowser + "
"
+ }
+ else {
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dropZoneElements = this._options.dragAndDrop.extraDropzones,
+ preventSelectFiles;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ dropZoneElements.push(this._find(this._options.element, 'drop'));
+ }
+
+ return new qq.DragAndDrop({
+ dropZoneElements: dropZoneElements,
+ hideDropZonesBeforeEnter: this._options.dragAndDrop.hideDropzones,
+ allowMultipleItems: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ processingDroppedFiles: function() {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ },
+ processingDroppedFilesComplete: function(files) {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ dropError: function(code, errorData) {
+ self._itemError(code, errorData);
+ },
+ dropLog: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type) {
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name) {
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ var onSuccessCallback = qq.bind(this._onSubmitDeleteSuccess, this, id);
+
+ qq.FineUploaderBasic.prototype._onSubmitDelete.call(this, id, onSuccessCallback);
+ },
+ _onSubmitDeleteSuccess: function(id) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ if (this._options.display.prependFiles) {
+ this._prependItem(item);
+ }
+ else {
+ this._listElement.appendChild(item);
+ }
+ this._filesInBatchAddedToUi += 1;
+
+ if (this._options.display.fileSizeOnSubmit && qq.supportedFeatures.ajaxUploading) {
+ this._displayFileSize(id);
+ }
+ },
+ _prependItem: function(item) {
+ var parentEl = this._listElement,
+ beforeEl = parentEl.firstChild;
+
+ if (this._totalFilesInBatch > 1 && this._filesInBatchAddedToUi > 0) {
+ beforeEl = qq(parentEl).children()[this._filesInBatchAddedToUi - 1].nextSibling;
+
+ }
+
+ parentEl.insertBefore(item, beforeEl);
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = item.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ },
+ _fileOrBlobRejected: function(id, name) {
+ this._totalFilesInBatch -= 1;
+ qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this, arguments);
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ this._totalFilesInBatch = items.length;
+ this._filesInBatchAddedToUi = 0;
+ qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this, arguments);
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, handlerImpl, api;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){},
+ onUuidChanged: function(id, newUuid){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.supportedFeatures.ajaxUploading) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, options.onUuidChanged, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, options.onUuidChanged, log);
+ }
+
+ function cancelSuccess(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ dequeue(id);
+ }
+
+
+ api = {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ handlerImpl.upload(id);
+ return true;
+ }
+
+ return false;
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ var cancelRetVal = handlerImpl.cancel(id);
+
+ if (qq.isPromise(cancelRetVal)) {
+ cancelRetVal.then(function() {
+ cancelSuccess(id);
+ });
+ }
+ else if (cancelRetVal !== false) {
+ cancelSuccess(id);
+ }
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ api.cancelAll();
+ queue = [];
+ handlerImpl.reset();
+ },
+ expunge: function(id) {
+ return handlerImpl.expunge(id);
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, onUuidChange, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ formHandlerInstanceId = qq.getUniqueId(),
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var iframeName = iframe.id,
+ fileId = getFileIdForIframeName(iframeName);
+
+ onloadCallbacks[uuids[fileId]] = callback;
+
+ detachLoadEvents[fileId] = qq(iframe).attach('load', function() {
+ if (inputs[fileId]) {
+ log("Received iframe load event for CORS upload request (iframe name " + iframeName + ")");
+
+ postMessageCallbackTimers[iframeName] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for iframe name " + iframeName;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(iframeName, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = parseResponse(getFileIdForIframeName(iframeName), message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ log("Handling response for iframe name " + iframeName);
+ clearTimeout(postMessageCallbackTimers[iframeName]);
+ delete postMessageCallbackTimers[iframeName];
+
+ detachLoadEvent(iframeName);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(iframeName);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(id, iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHtml = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHtml);
+ //plain text response may be wrapped in tag
+ if (innerHtml && innerHtml.match(/^ ');
+
+ iframe.setAttribute('id', iframeName);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+ function expungeFile(id) {
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(getIframeName(id));
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ }
+
+ function getFileIdForIframeName(iframeName) {
+ return iframeName.split("_")[0];
+ }
+
+ function getIframeName(fileId) {
+ return fileId + "_" + formHandlerInstanceId;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ formHandlerInstanceId = qq.getUniqueId();
+ },
+ expunge: function(id) {
+ return expungeFile(id);
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, api.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeFile(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeFile(id);
+ return true;
+ }
+
+ return false;
+ },
+
+ upload: function(id) {
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, api.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(id, iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, onUuidChange, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.supportedFeatures.chunking,
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.supportedFeatures.resume,
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+ options.onComplete(id, name, response, xhr);
+
+ if (fileState[id]) {
+ delete fileState[id].xhr;
+ }
+
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(id, xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+
+ if (response.newUuid !== undefined) {
+ log("Server requested UUID change from '" + fileState[id].uuid + "' to '" + response.newUuid + "'");
+ fileState[id].uuid = response.newUuid;
+ onUuidChanged(id, response.newUuid);
+ }
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(id, xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex) {
+ var currentChunkIndex;
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+
+ function handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex) {
+ var name = api.getName(id),
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part),
+ onResumeRetVal;
+
+ onResumeRetVal = options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume));
+ if (qq.isPromise(onResumeRetVal)) {
+ log("Waiting for onResume promise to be fulfilled for " + id);
+ onResumeRetVal.then(
+ function() {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ },
+ function() {
+ log("onResume promise fulfilled - failure indicated. Will not resume.")
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ );
+ }
+ else if (onResumeRetVal !== false) {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ }
+ else {
+ log("onResume callback returned false. Will not resume.");
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var firstChunkIndex = 0,
+ persistedChunkInfoForResume;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex);
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ uploadNextChunk(id);
+ }
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+ function expungeItem(id) {
+ var xhr = fileState[id].xhr;
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ }
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id, persistedChunkData,
+ uuid = qq.getUniqueId();
+
+ if (qq.isFile(fileOrBlobData)) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (qq.isBlob(fileOrBlobData.blob)) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ if (resumeEnabled) {
+ persistedChunkData = getPersistedChunkData(id);
+
+ if (persistedChunkData) {
+ uuid = persistedChunkData.uuid;
+ }
+ }
+
+ fileState[id].uuid = uuid;
+
+ return id;
+ },
+ getName: function(id){
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ expunge: function(id) {
+ return expungeItem(id);
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry) {
+ var name = this.getName(id);
+
+ if (this.isValid(id)) {
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ }
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, this.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeItem(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeItem(id);
+ return true;
+ }
+
+ return false;
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var uploader, $el, init, dataStore, pluginOption, pluginOptions, addCallbacks, transformVariables, isValidCommand,
+ delegateCommand;
+
+ pluginOptions = ['uploaderType'];
+
+ init = function (options) {
+ if (options) {
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+
+ if (pluginOption('uploaderType') === 'basic') {
+ uploader(new qq.FineUploaderBasic(xformedOpts));
+ }
+ else {
+ uploader(new qq.FineUploader(xformedOpts));
+ }
+ }
+
+ return $el;
+ };
+
+ dataStore = function(key, val) {
+ var data = $el.data('fineuploader');
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data('fineuploader', data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ //the underlying Fine Uploader instance is stored in jQuery's data stored, associated with the element
+ // tied to this instance of the plug-in
+ uploader = function(instanceToStore) {
+ return dataStore('uploader', instanceToStore);
+ };
+
+ pluginOption = function(option, optionVal) {
+ return dataStore(option, optionVal);
+ };
+
+ //implement all callbacks defined in Fine Uploader as functions that trigger appropriately names events and
+ // return the result of executing the bound handler back to Fine Uploader
+ addCallbacks = function(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {},
+ uploaderInst = new qq.FineUploaderBasic();
+
+ $.each(uploaderInst._options.callbacks, function(prop, func) {
+ var name, $callbackEl;
+
+ name = /^on(\w+)/.exec(prop)[1];
+ name = name.substring(0, 1).toLowerCase() + name.substring(1);
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var args = Array.prototype.slice.call(arguments);
+
+ return $callbackEl.triggerHandler(name, args);
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ transformVariables = function(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ if (source.uploaderType !== 'basic') {
+ xformed = { element : $el[0] };
+ }
+ else {
+ xformed = {};
+ }
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if ($.inArray(prop, pluginOptions) >= 0) {
+ pluginOption(prop, val);
+ }
+ else if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ isValidCommand = function(command) {
+ return $.type(command) === "string" &&
+ !command.match(/^_/) && //enforce private methods convention
+ uploader()[command] !== undefined;
+ };
+
+ //assuming we have already verified that this is a valid command, call the associated function in the underlying
+ // Fine Uploader instance (passing along the arguments from the caller) and return the result of the call back to the caller
+ delegateCommand = function(command) {
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
+
+ transformVariables(origArgs, xformedArgs);
+
+ return uploader()[command].apply(uploader(), xformedArgs);
+ };
+
+ $.fn.fineUploader = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (uploader() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error('Method ' + optionsOrCommand + ' does not exist on jQuery.fineUploader');
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
+/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var rootDataKey = "fineUploaderDnd",
+ $el;
+
+ function init (options) {
+ if (!options) {
+ options = {};
+ }
+
+ options.dropZoneElements = [$el];
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+ dnd(new qq.DragAndDrop(xformedOpts));
+
+ return $el;
+ };
+
+ function dataStore(key, val) {
+ var data = $el.data(rootDataKey);
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data(rootDataKey, data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ function dnd(instanceToStore) {
+ return dataStore('dndInstance', instanceToStore);
+ };
+
+ function addCallbacks(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {},
+ dndInst = new qq.FineUploaderBasic();
+
+ $.each(new qq.DragAndDrop.callbacks(), function(prop, func) {
+ var name = prop,
+ $callbackEl;
+
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var args = Array.prototype.slice.call(arguments),
+ jqueryHandlerResult = $callbackEl.triggerHandler(name, args);
+
+ return jqueryHandlerResult;
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ function transformVariables(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ xformed = {};
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ function isValidCommand(command) {
+ return $.type(command) === "string" &&
+ command === "dispose" &&
+ dnd()[command] !== undefined;
+ };
+
+ function delegateCommand(command) {
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
+ transformVariables(origArgs, xformedArgs);
+ return dnd()[command].apply(dnd(), xformedArgs);
+ };
+
+ $.fn.fineUploaderDnd = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (dnd() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error("Method " + optionsOrCommand + " does not exist in Fine Uploader's DnD module.");
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
diff --git a/ajax/libs/file-uploader/3.6.1/fineuploader-jquery.min.js b/ajax/libs/file-uploader/3.6.1/fineuploader-jquery.min.js
new file mode 100644
index 000000000..981b73a7c
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.1/fineuploader-jquery.min.js
@@ -0,0 +1,16 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: 3.6.1
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!=null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return Object.prototype.toString.call(variable)==="[object Object]"};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isArray=function(variable){"use strict";return Object.prototype.toString.call(variable)==="[object Array]"};qq.isString=function(maybeString){"use strict";return Object.prototype.toString.call(maybeString)==="[object String]"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFile=function(maybeFile){"use strict";return window.File&&Object.prototype.toString.call(maybeFile)==="[object File]"};qq.isFileList=function(maybeFileList){return window.FileList&&Object.prototype.toString.call(maybeFileList)==="[object FileList]"};qq.isFileOrInput=function(maybeFileOrInput){"use strict";return qq.isFile(maybeFileOrInput)||qq.isInput(maybeFileOrInput)};qq.isInput=function(maybeInput){if(window.HTMLInputElement){if(Object.prototype.toString.call(maybeInput)==="[object HTMLInputElement]"){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}else if(maybeInput.tagName){if(maybeInput.tagName.toLowerCase()==="input"){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&Object.prototype.toString.call(maybeBlob)==="[object Blob]"};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice!==undefined||File.prototype.webkitSlice!==undefined||File.prototype.mozSlice!==undefined)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}});return first};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{defaultResponseError:"Upload failure reason unknown",sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"misc_data",paramNames:{name:"qqblobname"}},paste:{targetElement:null,defaultName:"pasted_image"},camera:{ios:false}};qq.extend(this._options,o,true);this._handleCameraAccess();this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData=this._createUploadDataTracker();this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}if(this._options.paste.targetElement){this._pasteHandler=this._createPasteHandler()}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader "+qq.version+"] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader "+qq.version+"] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},getNetUploads:function(){return this._netUploaded},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._netUploadedOrQueued++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset();this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData.reset();if(this._pasteHandler){this._pasteHandler.reset()}},addFiles:function(filesOrInputs,params,endpoint){var self=this,verifiedFilesOrInputs=[],fileOrInputIndex,fileOrInput,fileIndex;if(filesOrInputs){if(!qq.isFileList(filesOrInputs)){filesOrInputs=[].concat(filesOrInputs)}for(fileOrInputIndex=0;fileOrInputIndex=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||qq.supportedFeatures.deleteFileCors)},_onSubmitDelete:function(id,onSuccessCallback){if(this._isDeletePossible()){return this._handleCheckedCallback({name:"onSubmitDelete",callback:qq.bind(this._options.callbacks.onSubmitDelete,this,id),onSuccess:onSuccessCallback||qq.bind(this._deleteHandler.sendDelete,this,id,this.getUuid(id)),identifier:id})}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(id){this._uploadData.setStatus(id,qq.status.DELETING)},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);
+if(isError){this._uploadData.setStatus(id,qq.status.DELETE_FAILED);this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status,xhr)}else{this._uploadData.setStatus(id,qq.status.DELETED);this._netUploadedOrQueued--;this._netUploaded--;this._handler.expunge(id);this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){this._uploadData.setStatus(id,qq.status.UPLOADING)},_onInputChange:function(input){if(qq.supportedFeatures.ajaxUploading){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0&&this._netUploadedOrQueued+1>itemLimit){this._itemError("retryFailTooManyItems","");return false}this.log("Retrying upload for '"+fileName+"' (id: "+id+")...");this._filesInProgress.push(id);return true}else{this.log("'"+id+"' is not a valid file ID","error");return false}},_maybeParseAndSendUploadError:function(id,name,response,xhr){if(!response.success){if(xhr&&xhr.status!==200&&!response.error){this._options.callbacks.onError(id,name,"XHR returned response code "+xhr.status,xhr)}else{var errorReason=response.error?response.error:this._options.text.defaultResponseError;this._options.callbacks.onError(id,name,errorReason,xhr)}}},_prepareItemsForUpload:function(items,params,endpoint){var validationDescriptors=this._getValidationDescriptors(items);this._handleCheckedCallback({name:"onValidateBatch",callback:qq.bind(this._options.callbacks.onValidateBatch,this,validationDescriptors),onSuccess:qq.bind(this._onValidateBatchCallbackSuccess,this,validationDescriptors,items,params,endpoint),identifier:"batch validation"})},_upload:function(blobOrFileContainer,params,endpoint){var id=this._handler.add(blobOrFileContainer),name=this._handler.getName(id);this._uploadData.added(id);if(params){this.setParams(params,id)}if(endpoint){this.setEndpoint(endpoint,id)}this._handleCheckedCallback({name:"onSubmit",callback:qq.bind(this._options.callbacks.onSubmit,this,id,name),onSuccess:qq.bind(this._onSubmitCallbackSuccess,this,id,name),onFailure:qq.bind(this._fileOrBlobRejected,this,id,name),identifier:id})},_onSubmitCallbackSuccess:function(id,name){this._uploadData.setStatus(id,qq.status.SUBMITTED);this._onSubmit(id,name);this._options.callbacks.onSubmitted(id,name);if(this._options.autoUpload){if(!this._handler.upload(id)){this._uploadData.setStatus(id,qq.status.QUEUED)}}else{this._storeForLater(id)}},_storeForLater:function(id){this._storedIds.push(id)},_onValidateBatchCallbackSuccess:function(validationDescriptors,items,params,endpoint){var errorMessage,itemLimit=this._options.validation.itemLimit,proposedNetFilesUploadedOrQueued=this._netUploadedOrQueued+validationDescriptors.length;if(itemLimit===0||proposedNetFilesUploadedOrQueued<=itemLimit){if(items.length>0){this._handleCheckedCallback({name:"onValidate",callback:qq.bind(this._options.callbacks.onValidate,this,items[0]),onSuccess:qq.bind(this._onValidateCallbackSuccess,this,items,0,params,endpoint),onFailure:qq.bind(this._onValidateCallbackFailure,this,items,0,params,endpoint),identifier:"Item '"+items[0].name+"', size: "+items[0].size})}else{this._itemError("noFilesError","")}}else{errorMessage=this._options.messages.tooManyItemsError.replace(/\{netItems\}/g,proposedNetFilesUploadedOrQueued).replace(/\{itemLimit\}/g,itemLimit);this._batchError(errorMessage)}},_onValidateCallbackSuccess:function(items,index,params,endpoint){var nextIndex=index+1,validationDescriptor=this._getValidationDescriptor(items[index]),validItem=false;if(this._validateFileOrBlobData(items[index],validationDescriptor)){validItem=true;this._upload(items[index],params,endpoint)}this._maybeProcessNextItemAfterOnValidateCallback(validItem,items,nextIndex,params,endpoint)},_onValidateCallbackFailure:function(items,index,params,endpoint){var nextIndex=index+1;this._fileOrBlobRejected(undefined,items[0].name);this._maybeProcessNextItemAfterOnValidateCallback(false,items,nextIndex,params,endpoint)},_maybeProcessNextItemAfterOnValidateCallback:function(validItem,items,index,params,endpoint){var self=this;if(items.length>index){if(validItem||!this._options.validation.stopOnFirstInvalidFile){setTimeout(function(){var validationDescriptor=self._getValidationDescriptor(items[index]);self._handleCheckedCallback({name:"onValidate",callback:qq.bind(self._options.callbacks.onValidate,self,items[index]),onSuccess:qq.bind(self._onValidateCallbackSuccess,self,items,index,params,endpoint),onFailure:qq.bind(self._onValidateCallbackFailure,self,items,index,params,endpoint),identifier:"Item '"+validationDescriptor.name+"', size: "+validationDescriptor.size})},0)}}},_validateFileOrBlobData:function(item,validationDescriptor){var name=validationDescriptor.name,size=validationDescriptor.size,valid=true;if(this._options.callbacks.onValidate(validationDescriptor)===false){valid=false}if(qq.isFileOrInput(item)&&!this._isAllowedExtension(name)){this._itemError("typeError",name);valid=false}else if(size===0){this._itemError("emptyError",name);valid=false}else if(size&&this._options.validation.sizeLimit&&size>this._options.validation.sizeLimit){this._itemError("sizeError",name);valid=false}else if(size&&size999);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size!==undefined){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}},_handleCameraAccess:function(){if(this._options.camera.ios&&qq.ios()){this._options.multiple=false;if(this._options.validation.acceptFiles===null){this._options.validation.acceptFiles="image/*;capture=camera"}else{this._options.validation.acceptFiles+=",image/*;capture=camera"}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,droppedFiles=[],disposeSupport=new qq.DisposeSupport;options={dropZoneElements:[],hideDropZonesBeforeEnter:false,allowMultipleItems:true,classes:{dropActive:null},callbacks:new qq.DragAndDrop.callbacks};qq.extend(options,o,true);setupDragDrop();function uploadDroppedFiles(files){options.callbacks.dropLog("Grabbed "+files.length+" dropped files.");dz.dropDisabled(false);options.callbacks.processingDroppedFilesComplete(files)}function traverseFileTree(entry){var dirReader,i,parseEntryPromise=new qq.Promise;if(entry.isFile){entry.file(function(file){droppedFiles.push(file);parseEntryPromise.success()},function(fileError){options.callbacks.dropLog("Problem parsing '"+entry.fullPath+"'. FileError code "+fileError.code+".","error");parseEntryPromise.failure()})}else if(entry.isDirectory){dirReader=entry.createReader();dirReader.readEntries(function(entries){var entriesLeft=entries.length;for(i=0;i1&&!options.allowMultipleItems){options.callbacks.processingDroppedFilesComplete([]);options.callbacks.dropError("tooManyFilesError","");dz.dropDisabled(false);handleDataTransferPromise.failure()}else{droppedFiles=[];if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file",unsupportedBrowser:"Unrecoverable error - this browser does not permit file uploading of any kind."},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false,prependFiles:false},paste:{promptForName:false,namePromptMessage:"Please name this image"},showMessage:function(message){setTimeout(function(){window.alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=window.confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)},showPrompt:function(message,defaultValue){var promise=new qq.Promise,retVal=window.prompt(message,defaultValue);if(retVal!=null&&qq.trimStr(retVal).length>0){promise.success(retVal)}else{promise.failure("Undefined or invalid user-supplied value.")}return promise}},true);qq.extend(this._options,o,true);if(!qq.supportedFeatures.uploading||this._options.cors.expected&&!qq.supportedFeatures.uploadCors){this._options.element.innerHTML=""+this._options.messages.unsupportedBrowser+"
"}else{this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop();if(this._options.paste.targetElement&&this._options.paste.promptForName){this._setupPastePrompt()}this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0}};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop();this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dropZoneElements=this._options.dragAndDrop.extraDropzones,preventSelectFiles;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){dropZoneElements.push(this._find(this._options.element,"drop"))}return new qq.DragAndDrop({dropZoneElements:dropZoneElements,hideDropZonesBeforeEnter:this._options.dragAndDrop.hideDropzones,allowMultipleItems:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{processingDroppedFiles:function(){var input=self._button.getInput();qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)},processingDroppedFilesComplete:function(files){var input=self._button.getInput();qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles);if(files){self.addFiles(files)}},dropError:function(code,errorData){self._itemError(code,errorData)},dropLog:function(message,level){self.log(message,level)}}})},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){var item=this.getItemByFileId(id);if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}else{qq(item).addClass(this._classes.retryable);return false}},_onSubmitDelete:function(id){var onSuccessCallback=qq.bind(this._onSubmitDeleteSuccess,this,id);qq.FineUploaderBasic.prototype._onSubmitDelete.call(this,id,onSuccessCallback)},_onSubmitDeleteSuccess:function(id){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}if(this._options.display.prependFiles){this._prependItem(item)}else{this._listElement.appendChild(item)}this._filesInBatchAddedToUi+=1;if(this._options.display.fileSizeOnSubmit&&qq.supportedFeatures.ajaxUploading){this._displayFileSize(id)}},_prependItem:function(item){var parentEl=this._listElement,beforeEl=parentEl.firstChild;if(this._totalFilesInBatch>1&&this._filesInBatchAddedToUi>0){beforeEl=qq(parentEl).children()[this._filesInBatchAddedToUi-1].nextSibling}parentEl.insertBefore(item,beforeEl)},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=item.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)
+}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_itemError:function(code,name){var message=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(message)},_batchError:function(message){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments);this._options.showMessage(message)},_setupPastePrompt:function(){var self=this;this._options.callbacks.onPasteReceived=function(){var message=self._options.paste.namePromptMessage,defaultVal=self._options.paste.defaultName;return self._options.showPrompt(message,defaultVal)}},_fileOrBlobRejected:function(id,name){this._totalFilesInBatch-=1;qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this,arguments)},_prepareItemsForUpload:function(items,params,endpoint){this._totalFilesInBatch=items.length;this._filesInBatchAddedToUi=0;qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this,arguments)}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,handlerImpl,api;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){},onUuidChanged:function(id,newUuid){}};qq.extend(options,o);log=options.log;function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){var cancelRetVal=handlerImpl.cancel(id);if(qq.isPromise(cancelRetVal)){cancelRetVal.then(function(){cancelSuccess(id)})}else if(cancelRetVal!==false){cancelSuccess(id)}},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},reset:function(){log("Resetting upload handler");api.cancelAll();queue=[];handlerImpl.reset()},expunge:function(id){return handlerImpl.expunge(id)},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}};return api};qq.UploadHandlerForm=function(o,uploadCompleteCallback,onUuidChange,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},formHandlerInstanceId=qq.getUniqueId(),api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var iframeName=iframe.id,fileId=getFileIdForIframeName(iframeName);onloadCallbacks[uuids[fileId]]=callback;detachLoadEvents[fileId]=qq(iframe).attach("load",function(){if(inputs[fileId]){log("Received iframe load event for CORS upload request (iframe name "+iframeName+")");postMessageCallbackTimers[iframeName]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for iframe name "+iframeName;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(iframeName,function(message){log("Received the following window message: '"+message+"'");var response=parseResponse(getFileIdForIframeName(iframeName),message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){log("Handling response for iframe name "+iframeName);clearTimeout(postMessageCallbackTimers[iframeName]);delete postMessageCallbackTimers[iframeName];detachLoadEvent(iframeName);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(iframeName);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(id,iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHtml=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHtml);if(innerHtml&&innerHtml.match(/^ ');iframe.setAttribute("id",iframeName);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}function expungeFile(id){delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(getIframeName(id));if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}}function getFileIdForIframeName(iframeName){return iframeName.split("_")[0]}function getIframeName(fileId){return fileId+"_"+formHandlerInstanceId}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){if(api.isValid(id)){return inputs[id].value.replace(/.*(\/|\\)/,"")}else{log(id+" is not a valid item ID.","error")}},isValid:function(id){return inputs[id]!==undefined},reset:function(){inputs=[];uuids=[];detachLoadEvents={};formHandlerInstanceId=qq.getUniqueId()},expunge:function(id){return expungeFile(id)},getUuid:function(id){return uuids[id]},cancel:function(id){var onCancelRetVal=options.onCancel(id,api.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeFile(id)})}else if(onCancelRetVal!==false){expungeFile(id);return true}return false},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,api.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(id,iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove()}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,onUuidChange,logCallback){"use strict";var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.supportedFeatures.chunking,resumeEnabled=options.resume.enabled&&chunkFiles&&qq.supportedFeatures.resume,resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);if(fileState[id]){delete fileState[id].xhr}uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(id,xhr){var response;try{response=qq.parseJson(xhr.responseText);if(response.newUuid!==undefined){log("Server requested UUID change from '"+fileState[id].uuid+"' to '"+response.newUuid+"'");fileState[id].uuid=response.newUuid;onUuidChanged(id,response.newUuid)}}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(id,xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex){var currentChunkIndex;for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}uploadNextChunk(id)}function onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex);calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}function handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex){var name=api.getName(id),firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part),onResumeRetVal;onResumeRetVal=options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume));if(qq.isPromise(onResumeRetVal)){log("Waiting for onResume promise to be fulfilled for "+id);onResumeRetVal.then(function(){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)},function(){log("onResume promise fulfilled - failure indicated. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)})}else if(onResumeRetVal!==false){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)}else{log("onResume callback returned false. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}function handleFileChunkingUpload(id,retry){var firstChunkIndex=0,persistedChunkInfoForResume;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex)}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{uploadNextChunk(id)}}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}function expungeItem(id){var xhr=fileState[id].xhr;if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]}api={add:function(fileOrBlobData){var id,persistedChunkData,uuid=qq.getUniqueId();if(qq.isFile(fileOrBlobData)){id=fileState.push({file:fileOrBlobData})-1}else if(qq.isBlob(fileOrBlobData.blob)){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}if(resumeEnabled){persistedChunkData=getPersistedChunkData(id);if(persistedChunkData){uuid=persistedChunkData.uuid}}fileState[id].uuid=uuid;return id},getName:function(id){if(api.isValid(id)){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}}else{log(id+" is not a valid item ID.","error")}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},expunge:function(id){return expungeItem(id)},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);if(this.isValid(id)){options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}}},cancel:function(id){var onCancelRetVal=options.onCancel(id,this.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeItem(id)})}else if(onCancelRetVal!==false){expungeItem(id);return true}return false},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};!function($){"use strict";var uploader,$el,init,dataStore,pluginOption,pluginOptions,addCallbacks,transformVariables,isValidCommand,delegateCommand;pluginOptions=["uploaderType"];init=function(options){if(options){var xformedOpts=transformVariables(options);addCallbacks(xformedOpts);if(pluginOption("uploaderType")==="basic"){uploader(new qq.FineUploaderBasic(xformedOpts))}else{uploader(new qq.FineUploader(xformedOpts))}}return $el};dataStore=function(key,val){var data=$el.data("fineuploader");if(val){if(data===undefined){data={}}data[key]=val;$el.data("fineuploader",data)}else{if(data===undefined){return null}return data[key]}};uploader=function(instanceToStore){return dataStore("uploader",instanceToStore)};pluginOption=function(option,optionVal){return dataStore(option,optionVal)};addCallbacks=function(transformedOpts){var callbacks=transformedOpts.callbacks={},uploaderInst=new qq.FineUploaderBasic;$.each(uploaderInst._options.callbacks,function(prop,func){var name,$callbackEl;name=/^on(\w+)/.exec(prop)[1];name=name.substring(0,1).toLowerCase()+name.substring(1);$callbackEl=$el;callbacks[prop]=function(){var args=Array.prototype.slice.call(arguments);return $callbackEl.triggerHandler(name,args)}})};transformVariables=function(source,dest){var xformed,arrayVals;if(dest===undefined){if(source.uploaderType!=="basic"){xformed={element:$el[0]}}else{xformed={}}}else{xformed=dest}$.each(source,function(prop,val){if($.inArray(prop,pluginOptions)>=0){pluginOption(prop,val)}else if(val instanceof $){xformed[prop]=val[0]}else if($.isPlainObject(val)){xformed[prop]={};transformVariables(val,xformed[prop])}else if($.isArray(val)){arrayVals=[];$.each(val,function(idx,arrayVal){if(arrayVal instanceof $){$.merge(arrayVals,arrayVal)}else{arrayVals.push(arrayVal)}});xformed[prop]=arrayVals}else{xformed[prop]=val}});if(dest===undefined){return xformed}};isValidCommand=function(command){return $.type(command)==="string"&&!command.match(/^_/)&&uploader()[command]!==undefined};delegateCommand=function(command){var xformedArgs=[],origArgs=Array.prototype.slice.call(arguments,1);transformVariables(origArgs,xformedArgs);return uploader()[command].apply(uploader(),xformedArgs)};$.fn.fineUploader=function(optionsOrCommand){var self=this,selfArgs=arguments,retVals=[];this.each(function(index,el){$el=$(el);if(uploader()&&isValidCommand(optionsOrCommand)){retVals.push(delegateCommand.apply(self,selfArgs));if(self.length===1){return false}}else if(typeof optionsOrCommand==="object"||!optionsOrCommand){init.apply(self,selfArgs)
+}else{$.error("Method "+optionsOrCommand+" does not exist on jQuery.fineUploader")}});if(retVals.length===1){return retVals[0]}else if(retVals.length>1){return retVals}return this}}(jQuery);!function($){"use strict";var rootDataKey="fineUploaderDnd",$el;function init(options){if(!options){options={}}options.dropZoneElements=[$el];var xformedOpts=transformVariables(options);addCallbacks(xformedOpts);dnd(new qq.DragAndDrop(xformedOpts));return $el}function dataStore(key,val){var data=$el.data(rootDataKey);if(val){if(data===undefined){data={}}data[key]=val;$el.data(rootDataKey,data)}else{if(data===undefined){return null}return data[key]}}function dnd(instanceToStore){return dataStore("dndInstance",instanceToStore)}function addCallbacks(transformedOpts){var callbacks=transformedOpts.callbacks={},dndInst=new qq.FineUploaderBasic;$.each(new qq.DragAndDrop.callbacks,function(prop,func){var name=prop,$callbackEl;$callbackEl=$el;callbacks[prop]=function(){var args=Array.prototype.slice.call(arguments),jqueryHandlerResult=$callbackEl.triggerHandler(name,args);return jqueryHandlerResult}})}function transformVariables(source,dest){var xformed,arrayVals;if(dest===undefined){xformed={}}else{xformed=dest}$.each(source,function(prop,val){if(val instanceof $){xformed[prop]=val[0]}else if($.isPlainObject(val)){xformed[prop]={};transformVariables(val,xformed[prop])}else if($.isArray(val)){arrayVals=[];$.each(val,function(idx,arrayVal){if(arrayVal instanceof $){$.merge(arrayVals,arrayVal)}else{arrayVals.push(arrayVal)}});xformed[prop]=arrayVals}else{xformed[prop]=val}});if(dest===undefined){return xformed}}function isValidCommand(command){return $.type(command)==="string"&&command==="dispose"&&dnd()[command]!==undefined}function delegateCommand(command){var xformedArgs=[],origArgs=Array.prototype.slice.call(arguments,1);transformVariables(origArgs,xformedArgs);return dnd()[command].apply(dnd(),xformedArgs)}$.fn.fineUploaderDnd=function(optionsOrCommand){var self=this,selfArgs=arguments,retVals=[];this.each(function(index,el){$el=$(el);if(dnd()&&isValidCommand(optionsOrCommand)){retVals.push(delegateCommand.apply(self,selfArgs));if(self.length===1){return false}}else if(typeof optionsOrCommand==="object"||!optionsOrCommand){init.apply(self,selfArgs)}else{$.error("Method "+optionsOrCommand+" does not exist in Fine Uploader's DnD module.")}});if(retVals.length===1){return retVals[0]}else if(retVals.length>1){return retVals}return this}}(jQuery);
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.1/fineuploader.css b/ajax/libs/file-uploader/3.6.1/fineuploader.css
new file mode 100644
index 000000000..11a436a06
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.1/fineuploader.css
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2013, Widen Enterprises info@fineuploader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+.qq-uploader {
+ position: relative;
+ width: 100%;
+}
+.qq-upload-button {
+ display: block;
+ width: 105px;
+ padding: 7px 0;
+ text-align: center;
+ background: #880000;
+ border-bottom: 1px solid #DDD;
+ color: #FFF;
+}
+.qq-upload-button-hover {
+ background: #CC0000;
+}
+.qq-upload-button-focus {
+ outline: 1px dotted #000000;
+}
+.qq-upload-drop-area, .qq-upload-extra-drop-area {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ min-height: 30px;
+ z-index: 2;
+ background: #FF9797;
+ text-align: center;
+}
+.qq-upload-drop-area span {
+ display: block;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ margin-top: -8px;
+ font-size: 16px;
+}
+.qq-upload-extra-drop-area {
+ position: relative;
+ margin-top: 50px;
+ font-size: 16px;
+ padding-top: 30px;
+ height: 20px;
+ min-height: 40px;
+}
+.qq-upload-drop-area-active {
+ background: #FF7171;
+}
+.qq-upload-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+.qq-upload-list li {
+ margin: 0;
+ padding: 9px;
+ line-height: 15px;
+ font-size: 16px;
+ background-color: #FFF0BD;
+}
+.qq-upload-file, .qq-upload-spinner, .qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text, .qq-upload-finished, .qq-upload-delete {
+ margin-right: 12px;
+}
+.qq-upload-file {
+}
+.qq-upload-spinner {
+ display: inline-block;
+ background: url("loading.gif");
+ width: 15px;
+ height: 15px;
+ vertical-align: text-bottom;
+}
+.qq-drop-processing {
+ display: none;
+}
+.qq-drop-processing-spinner {
+ display: inline-block;
+ background: url("processing.gif");
+ width: 24px;
+ height: 24px;
+ vertical-align: text-bottom;
+}
+.qq-upload-finished {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-retry, .qq-upload-delete {
+ display: none;
+ color: #000000;
+}
+.qq-upload-cancel, .qq-upload-delete {
+ color: #000000;
+}
+.qq-upload-retryable .qq-upload-retry {
+ display: inline;
+}
+.qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-delete {
+ font-size: 12px;
+ font-weight: normal;
+}
+.qq-upload-failed-text {
+ display: none;
+ font-style: italic;
+ font-weight: bold;
+}
+.qq-upload-failed-icon {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-fail .qq-upload-failed-text {
+ display: inline;
+}
+.qq-upload-retrying .qq-upload-failed-text {
+ display: inline;
+ color: #D60000;
+}
+.qq-upload-list li.qq-upload-success {
+ background-color: #5DA30C;
+ color: #FFFFFF;
+}
+.qq-upload-list li.qq-upload-fail {
+ background-color: #D60000;
+ color: #FFFFFF;
+}
+.qq-progress-bar {
+ background: -moz-linear-gradient(top, rgba(30,87,153,1) 0%, rgba(41,137,216,1) 50%, rgba(32,124,202,1) 51%, rgba(125,185,232,1) 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(30,87,153,1)), color-stop(50%,rgba(41,137,216,1)), color-stop(51%,rgba(32,124,202,1)), color-stop(100%,rgba(125,185,232,1))); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* IE10+ */
+ background: linear-gradient(to bottom, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* W3C */
+ width: 0%;
+ height: 15px;
+ border-radius: 6px;
+ margin-bottom: 3px;
+ display: none;
+}
diff --git a/ajax/libs/file-uploader/3.6.1/fineuploader.js b/ajax/libs/file-uploader/3.6.1/fineuploader.js
new file mode 100644
index 000000000..86a680997
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.1/fineuploader.js
@@ -0,0 +1,4724 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: 3.6.1
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity != null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return Object.prototype.toString.call(variable) === '[object Object]';
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isArray = function(variable) {
+ "use strict";
+ return Object.prototype.toString.call(variable) === "[object Array]";
+}
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFile = function(maybeFile) {
+ "use strict";
+
+ return window.File && Object.prototype.toString.call(maybeFile) === '[object File]'
+};
+
+qq.isFileList = function(maybeFileList) {
+ return window.FileList && Object.prototype.toString.call(maybeFileList) === '[object FileList]'
+}
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+
+ return qq.isFile(maybeFileOrInput) || qq.isInput(maybeFileOrInput);
+};
+
+qq.isInput = function(maybeInput) {
+ if (window.HTMLInputElement) {
+ if (Object.prototype.toString.call(maybeInput) === '[object HTMLInputElement]') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ else if (maybeInput.tagName) {
+ if (maybeInput.tagName.toLowerCase() === 'input') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && Object.prototype.toString.call(maybeBlob) === '[object Blob]';
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice !== undefined || File.prototype.webkitSlice !== undefined || File.prototype.mozSlice !== undefined);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+
+ return first;
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+qq.ios = function() {
+ "use strict";
+ return navigator.userAgent.indexOf("iPad") !== -1
+ || navigator.userAgent.indexOf("iPod") !== -1
+ || navigator.userAgent.indexOf("iPhone") !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(objOrArray, callback) {
+ "use strict";
+ var keyOrIndex, retVal;
+ if (objOrArray) {
+ if (qq.isArray(objOrArray)) {
+ for (keyOrIndex = 0; keyOrIndex < objOrArray.length; keyOrIndex++) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ else {
+ for (keyOrIndex in objOrArray) {
+ if (Object.prototype.hasOwnProperty.call(objOrArray, keyOrIndex)) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+ }
+};
+
+//include any args that should be passed to the new function after the context arg
+qq.bind = function(oldFunc, context) {
+ if (qq.isFunction(oldFunc)) {
+ var args = Array.prototype.slice.call(arguments, 2);
+
+ return function() {
+ if (arguments.length) {
+ args = args.concat(Array.prototype.slice.call(arguments))
+ }
+ return oldFunc.apply(context, args);
+ };
+ }
+
+ throw new Error("first parameter must be a function!");
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ cookie;
+
+ qq.each(ca, function(idx, part) {
+ var cookiePart = part;
+ while (cookiePart.charAt(0)==' ') {
+ cookiePart = cookiePart.substring(1, cookiePart.length);
+ }
+
+ if (cookiePart.indexOf(nameEQ) === 0) {
+ cookie = cookiePart.substring(nameEQ.length, cookiePart.length);
+ return false;
+ }
+ });
+
+ return cookie;
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+qq.version="-unstable-";qq.supportedFeatures = (function() {
+ var supportsUploading,
+ supportsAjaxFileUploading,
+ supportsFolderDrop,
+ supportsChunking,
+ supportsResume,
+ supportsUploadViaPaste,
+ supportsUploadCors,
+ supportsDeleteFileCors;
+
+
+ function testSupportsFileInputElement() {
+ var supported = true,
+ tempInput;
+
+ try {
+ tempInput = document.createElement('input');
+ tempInput.type = 'file';
+ qq(tempInput).hide();
+
+ if(tempInput.disabled) {
+ supported = false;
+ }
+ }
+ catch(ex) {
+ supported = false;
+ }
+
+ return supported;
+ }
+
+ //only way to test for Filesystem API support since webkit does not expose the DataTransfer interface
+ function isChrome21OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[2][1-9]|Chrome\/[3-9][0-9]/) !== undefined;
+ }
+
+ //only way to test for complete Clipboard API support at this time
+ function isChrome14OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/) !== undefined;
+ }
+
+
+ supportsUploading = testSupportsFileInputElement();
+
+ supportsAjaxFileUploading = supportsUploading && qq.isXhrUploadSupported();
+
+ supportsFolderDrop = supportsAjaxFileUploading && isChrome21OrHigher();
+
+ supportsChunking = supportsAjaxFileUploading && qq.isFileChunkingSupported();
+
+ supportsResume = supportsAjaxFileUploading && supportsChunking && qq.areCookiesEnabled();
+
+ supportsUploadViaPaste = supportsAjaxFileUploading && isChrome14OrHigher();
+
+ supportsUploadCors = supportsUploading && (window.postMessage !== undefined || supportsAjaxFileUploading);
+
+ supportsDeleteFileCors = supportsAjaxFileUploading;
+
+
+ return {
+ uploading: supportsUploading,
+ ajaxUploading: supportsAjaxFileUploading,
+ fileDrop: supportsAjaxFileUploading, //NOTE: will also return true for touch-only devices. It's not currently possible to accurately test for touch-only devices
+ folderDrop: supportsFolderDrop,
+ chunking: supportsChunking,
+ resume: supportsResume,
+ uploadCustomHeaders: supportsAjaxFileUploading,
+ uploadNonMultipart: supportsAjaxFileUploading,
+ itemSizeValidation: supportsAjaxFileUploading,
+ uploadViaPaste: supportsUploadViaPaste,
+ progressBar: supportsAjaxFileUploading,
+ uploadCors: supportsUploadCors,
+ deleteFileCors: supportsDeleteFileCors,
+ canDetermineSize: supportsAjaxFileUploading
+ }
+
+}());
+/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallbacks = [],
+ failureCallbacks = [],
+ doneCallbacks = [],
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ if (onSuccess) {
+ successCallbacks.push(onSuccess);
+ }
+ if (onFailure) {
+ failureCallbacks.push(onFailure);
+ }
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+
+ return this;
+ },
+
+ done: function(callback) {
+ if (state === 0) {
+ doneCallbacks.push(callback);
+ }
+ else {
+ callback();
+ }
+
+ return this;
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallbacks.length) {
+ qq.each(successCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallbacks.length) {
+ qq.each(failureCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ }
+ };
+};
+
+qq.isPromise = function(maybePromise) {
+ return maybePromise && maybePromise.then && maybePromise.done;
+};/*globals qq*/
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ disposeSupport = new qq.DisposeSupport(),
+ options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input) {},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+
+ qq.extend(options, o);
+
+ // make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};qq.UploadData = function(uploaderProxy) {
+ var data = [],
+ byId = {},
+ byUuid = {},
+ byStatus = {},
+ api;
+
+ function getDataByIds(ids) {
+ if (qq.isArray(ids)) {
+ var entries = [];
+
+ qq.each(ids, function(idx, id) {
+ entries.push(data[byId[id]]);
+ });
+
+ return entries;
+ }
+
+ return data[byId[ids]];
+ }
+
+ function getDataByUuids(uuids) {
+ if (qq.isArray(uuids)) {
+ var entries = [];
+
+ qq.each(uuids, function(idx, uuid) {
+ entries.push(data[byUuid[uuid]]);
+ });
+
+ return entries;
+ }
+
+ return data[byUuid[uuids]];
+ }
+
+ function getDataByStatus(status) {
+ var statusResults = [],
+ statuses = [].concat(status);
+
+ qq.each(statuses, function(index, statusEnum) {
+ var statusResultIndexes = byStatus[statusEnum];
+
+ if (statusResultIndexes !== undefined) {
+ qq.each(statusResultIndexes, function(i, dataIndex) {
+ statusResults.push(data[dataIndex]);
+ });
+ }
+ });
+
+ return statusResults;
+ }
+
+ api = {
+ added: function(id) {
+ var uuid = uploaderProxy.getUuid(id),
+ name = uploaderProxy.getName(id),
+ size = uploaderProxy.getSize(id),
+ status = qq.status.SUBMITTING;
+
+ var index = data.push({
+ id: id,
+ name: name,
+ uuid: uuid,
+ size: size,
+ status: status
+ }) - 1;
+
+ byId[id] = index;
+
+ byUuid[uuid] = index;
+
+ if (byStatus[status] === undefined) {
+ byStatus[status] = [];
+ }
+ byStatus[status].push(index);
+
+ uploaderProxy.onStatusChange(id, undefined, status);
+ },
+
+ retrieve: function(optionalFilter) {
+ if (qq.isObject(optionalFilter) && data.length) {
+ if (optionalFilter.id !== undefined) {
+ return getDataByIds(optionalFilter.id);
+ }
+
+ else if (optionalFilter.uuid !== undefined) {
+ return getDataByUuids(optionalFilter.uuid);
+ }
+
+ else if (optionalFilter.status) {
+ return getDataByStatus(optionalFilter.status);
+ }
+ }
+ else {
+ return qq.extend([], data, true);
+ }
+ },
+
+ reset: function() {
+ data = [];
+ byId = {};
+ byUuid = {};
+ byStatus = {};
+ },
+
+ setStatus: function(id, newStatus) {
+ var dataIndex = byId[id],
+ oldStatus = data[dataIndex].status,
+ byStatusOldStatusIndex = qq.indexOf(byStatus[oldStatus], dataIndex);
+
+ byStatus[oldStatus].splice(byStatusOldStatusIndex, 1);
+
+ data[dataIndex].status = newStatus;
+
+ if (byStatus[newStatus] === undefined) {
+ byStatus[newStatus] = [];
+ }
+ byStatus[newStatus].push(dataIndex);
+
+ uploaderProxy.onStatusChange(id, oldStatus, newStatus);
+ },
+
+ uuidChanged: function(id, newUuid) {
+ var dataIndex = byId[id],
+ oldUuid = data[dataIndex].uuid;
+
+ data[dataIndex].uuid = newUuid;
+ byUuid[newUuid] = dataIndex;
+ delete byUuid[oldUuid];
+ }
+ };
+
+ return api;
+};
+
+qq.status = {
+ SUBMITTING: "submitting",
+ SUBMITTED: "submitted",
+ REJECTED: "rejected",
+ QUEUED: "queued",
+ CANCELED: "canceled",
+ UPLOADING: "uploading",
+ UPLOAD_RETRYING: "retrying upload",
+ UPLOAD_SUCCESSFUL: "upload successful",
+ UPLOAD_FAILED: "upload failed",
+ DELETE_FAILED: "delete failed",
+ DELETING: "deleting",
+ DELETED: "deleted"
+};qq.FineUploaderBasic = function(o) {
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true,
+ acceptFiles: null
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON, maybeXhr){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){},
+ onPasteReceived: function(blob) {},
+ onStatusChange: function(id, oldStatus, newStatus) {}
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ defaultResponseError: "Upload failure reason unknown",
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'misc_data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ },
+ camera: {
+ ios: false
+ }
+ };
+
+ qq.extend(this._options, o, true);
+
+ this._handleCameraAccess();
+
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData = this._createUploadDataTracker();
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader ' + qq.version + '] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader ' + qq.version + '] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function() {
+ return this._filesInProgress.length;
+ },
+ getNetUploads: function() {
+ return this._netUploaded;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netUploadedOrQueued++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData.reset();
+
+ if (this._pasteHandler) {
+ this._pasteHandler.reset();
+ }
+ },
+ addFiles: function(filesOrInputs, params, endpoint) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ fileOrInputIndex, fileOrInput, fileIndex;
+
+ if (filesOrInputs) {
+ if (!qq.isFileList(filesOrInputs)) {
+ filesOrInputs = [].concat(filesOrInputs);
+ }
+
+ for (fileOrInputIndex = 0; fileOrInputIndex < filesOrInputs.length; fileOrInputIndex+=1) {
+ fileOrInput = filesOrInputs[fileOrInputIndex];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ if (qq.isInput(fileOrInput) && qq.supportedFeatures.ajaxUploading) {
+ for (fileIndex = 0; fileIndex < fileOrInput.files.length; fileIndex++) {
+ verifiedFilesOrInputs.push(fileOrInput.files[fileIndex]);
+ }
+ }
+ else {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Received ' + verifiedFilesOrInputs.length + ' files or inputs.');
+ this._prepareItemsForUpload(verifiedFilesOrInputs, params, endpoint);
+ }
+ },
+ addBlobs: function(blobDataOrArray, params, endpoint) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._prepareItemsForUpload(verifiedBlobDataList, params, endpoint);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ doesExist: function(fileOrBlobId) {
+ return this._handler.isValid(fileOrBlobId);
+ },
+ getUploads: function(optionalFilter) {
+ return this._uploadData.retrieve(optionalFilter);
+ },
+ _handleCheckedCallback: function(details) {
+ var self = this,
+ callbackRetVal = details.callback();
+
+ if (qq.isPromise(callbackRetVal)) {
+ this.log(details.name + " - waiting for " + details.name + " promise to be fulfilled for " + details.identifier);
+ return callbackRetVal.then(
+ function(successParam) {
+ self.log(details.name + " promise success for " + details.identifier);
+ details.onSuccess(successParam);
+ },
+ function() {
+ if (details.onFailure) {
+ self.log(details.name + " promise failure for " + details.identifier);
+ details.onFailure();
+ }
+ else {
+ self.log(details.name + " promise failure for " + details.identifier);
+ }
+ });
+ }
+
+ if (callbackRetVal !== false) {
+ details.onSuccess(callbackRetVal);
+ }
+ else {
+ if (details.onFailure) {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Invoking failure callback.")
+ details.onFailure();
+ }
+ else {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Will not proceed.")
+ }
+ }
+
+ return callbackRetVal;
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.supportedFeatures.ajaxUploading,
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result, xhr);
+ },
+ onCancel: function(id, name) {
+ return self._handleCheckedCallback({
+ name: "onCancel",
+ callback: qq.bind(self._options.callbacks.onCancel, self, id, name),
+ onSuccess: qq.bind(self._onCancel, self, id, name),
+ identifier: id
+ });
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ onUuidChanged: function(id, newUuid) {
+ self._uploadData.uuidChanged(id, newUuid);
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ self._handleCheckedCallback({
+ name: "onPasteReceived",
+ callback: qq.bind(self._options.callbacks.onPasteReceived, self, blob),
+ onSuccess: qq.bind(self._handlePasteSuccess, self, blob),
+ identifier: "pasted image"
+ });
+ }
+ }
+ });
+ },
+ _createUploadDataTracker: function() {
+ var self = this;
+
+ return new qq.UploadData({
+ getName: function(id) {
+ return self.getName(id);
+ },
+ getUuid: function(id) {
+ return self.getUuid(id);
+ },
+ getSize: function(id) {
+ return self.getSize(id);
+ },
+ onStatusChange: function(id, oldStatus, newStatus) {
+ self._options.callbacks.onStatusChange(id, oldStatus, newStatus);
+ }
+ });
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total) {
+ //nothing to do yet in core uploader
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_FAILED);
+ this._netUploadedOrQueued--;
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_SUCCESSFUL);
+ this._netUploaded++;
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.CANCELED);
+
+ this._netUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected || qq.supportedFeatures.deleteFileCors));
+ },
+ _onSubmitDelete: function(id, onSuccessCallback) {
+ if (this._isDeletePossible()) {
+ return this._handleCheckedCallback({
+ name: "onSubmitDelete",
+ callback: qq.bind(this._options.callbacks.onSubmitDelete, this, id),
+ onSuccess: onSuccessCallback || qq.bind(this._deleteHandler.sendDelete, this, id, this.getUuid(id)),
+ identifier: id
+ });
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(id) {
+ this._uploadData.setStatus(id, qq.status.DELETING);
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this._uploadData.setStatus(id, qq.status.DELETE_FAILED);
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.DELETED);
+ this._netUploadedOrQueued--;
+ this._netUploaded--;
+ this._handler.expunge(id);
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.UPLOADING);
+ },
+ _onInputChange: function(input){
+ if (qq.supportedFeatures.ajaxUploading) {
+ this.addFiles(input.files);
+ }
+ else {
+ this.addFiles(input);
+ }
+
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems", "");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : this._options.text.defaultResponseError;
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ var validationDescriptors = this._getValidationDescriptors(items);
+
+ this._handleCheckedCallback({
+ name: "onValidateBatch",
+ callback: qq.bind(this._options.callbacks.onValidateBatch, this, validationDescriptors),
+ onSuccess: qq.bind(this._onValidateBatchCallbackSuccess, this, validationDescriptors, items, params, endpoint),
+ identifier: "batch validation"
+ });
+ },
+ _upload: function(blobOrFileContainer, params, endpoint) {
+ var id = this._handler.add(blobOrFileContainer),
+ name = this._handler.getName(id);
+
+ this._uploadData.added(id);
+
+ if (params) {
+ this.setParams(params, id);
+ }
+
+ if (endpoint) {
+ this.setEndpoint(endpoint, id);
+ }
+
+ this._handleCheckedCallback({
+ name: "onSubmit",
+ callback: qq.bind(this._options.callbacks.onSubmit, this, id, name),
+ onSuccess: qq.bind(this._onSubmitCallbackSuccess, this, id, name),
+ onFailure: qq.bind(this._fileOrBlobRejected, this, id, name),
+ identifier: id
+ });
+ },
+ _onSubmitCallbackSuccess: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.SUBMITTED);
+
+ this._onSubmit(id, name);
+ this._options.callbacks.onSubmitted(id, name);
+
+ if (this._options.autoUpload) {
+ if (!this._handler.upload(id)) {
+ this._uploadData.setStatus(id, qq.status.QUEUED);
+ }
+ }
+ else {
+ this._storeForLater(id);
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _onValidateBatchCallbackSuccess: function(validationDescriptors, items, params, endpoint) {
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netUploadedOrQueued + validationDescriptors.length;
+
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ if (items.length > 0) {
+ this._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(this._options.callbacks.onValidate, this, items[0]),
+ onSuccess: qq.bind(this._onValidateCallbackSuccess, this, items, 0, params, endpoint),
+ onFailure: qq.bind(this._onValidateCallbackFailure, this, items, 0, params, endpoint),
+ identifier: "Item '" + items[0].name + "', size: " + items[0].size
+ });
+ }
+ else {
+ this._itemError("noFilesError", "");
+ }
+ }
+ else {
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ },
+ _onValidateCallbackSuccess: function(items, index, params, endpoint) {
+ var nextIndex = index+1,
+ validationDescriptor = this._getValidationDescriptor(items[index]),
+ validItem = false;
+
+ if (this._validateFileOrBlobData(items[index], validationDescriptor)) {
+ validItem = true;
+ this._upload(items[index], params, endpoint);
+ }
+
+ this._maybeProcessNextItemAfterOnValidateCallback(validItem, items, nextIndex, params, endpoint);
+ },
+ _onValidateCallbackFailure: function(items, index, params, endpoint) {
+ var nextIndex = index+ 1;
+
+ this._fileOrBlobRejected(undefined, items[0].name);
+
+ this._maybeProcessNextItemAfterOnValidateCallback(false, items, nextIndex, params, endpoint);
+ },
+ _maybeProcessNextItemAfterOnValidateCallback: function(validItem, items, index, params, endpoint) {
+ var self = this;
+
+ if (items.length > index) {
+ if (validItem || !this._options.validation.stopOnFirstInvalidFile) {
+ //use setTimeout to prevent a stack overflow with a large number of files in the batch & non-promissory callbacks
+ setTimeout(function() {
+ var validationDescriptor = self._getValidationDescriptor(items[index]);
+
+ self._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(self._options.callbacks.onValidate, self, items[index]),
+ onSuccess: qq.bind(self._onValidateCallbackSuccess, self, items, index, params, endpoint),
+ onFailure: qq.bind(self._onValidateCallbackFailure, self, items, index, params, endpoint),
+ identifier: "Item '" + validationDescriptor.name + "', size: " + validationDescriptor.size
+ });
+ }, 0);
+ }
+ }
+ },
+ _validateFileOrBlobData: function(item, validationDescriptor) {
+ var name = validationDescriptor.name,
+ size = validationDescriptor.size,
+ valid = true;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ valid = false;
+ }
+
+ if (qq.isFileOrInput(item) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ valid = false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ valid = false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ valid = false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ valid = false;
+ }
+
+ if (!valid) {
+ this._fileOrBlobRejected(undefined, name);
+ }
+
+ return valid;
+ },
+ _fileOrBlobRejected: function(id, name) {
+ if (id !== undefined) {
+ this._uploadData.setStatus(id, qq.status.REJECTED);
+ }
+ },
+ _itemError: function(code, nameOrNames) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ names = [].concat(nameOrNames),
+ name = names[0],
+ extensionsForMessage, placeholderMatch;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ placeholderMatch = message.match(/(\{\w+\})/g);
+ if (placeholderMatch !== null) {
+ qq.each(placeholderMatch, function(idx, placeholder) {
+ r(placeholder, names[idx]);
+ });
+ }
+
+ this._options.callbacks.onError(null, name, message, undefined);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message, undefined);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1000;
+ i++;
+ } while (bytes > 999);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size !== undefined) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ },
+ _handleCameraAccess: function() {
+ if (this._options.camera.ios && qq.ios()) {
+ this._options.multiple = false;
+
+ if (this._options.validation.acceptFiles === null) {
+ this._options.validation.acceptFiles = "image/*;capture=camera";
+ }
+ else {
+ this._options.validation.acceptFiles += ",image/*;capture=camera";
+ }
+ }
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz,
+ droppedFiles = [],
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropZoneElements: [],
+ hideDropZonesBeforeEnter: false,
+ allowMultipleItems: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: new qq.DragAndDrop.callbacks()
+ };
+
+ qq.extend(options, o, true);
+
+ setupDragDrop();
+
+ function uploadDroppedFiles(files) {
+ options.callbacks.dropLog('Grabbed ' + files.length + " dropped files.");
+ dz.dropDisabled(false);
+ options.callbacks.processingDroppedFilesComplete(files);
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i,
+ parseEntryPromise = new qq.Promise();
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ droppedFiles.push(file);
+ parseEntryPromise.success();
+ },
+ function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+ else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ var entriesLeft = entries.length;
+
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]).done(function() {
+ entriesLeft-=1;
+
+ if (entriesLeft === 0) {
+ parseEntryPromise.success();
+ }
+ });
+ }
+
+ if (!entries.length) {
+ parseEntryPromise.success();
+ }
+ }, function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+
+ return parseEntryPromise;
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry,
+ pendingFolderPromises = [],
+ handleDataTransferPromise = new qq.Promise();
+
+ options.callbacks.processingDroppedFiles();
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.allowMultipleItems) {
+ options.callbacks.processingDroppedFilesComplete([]);
+ options.callbacks.dropError('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ handleDataTransferPromise.failure();
+ }
+ else {
+ droppedFiles = [];
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ }
+
+ else {
+ pendingFolderPromises.push(traverseFileTree(entry).done(function() {
+ pendingFolderPromises.pop();
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }));
+ }
+ }
+ }
+ }
+ else {
+ droppedFiles = dataTransfer.files;
+ }
+
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }
+
+ return handleDataTransferPromise;
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer).done(function() {
+ uploadDroppedFiles(droppedFiles);
+ });
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ var dropZones = options.dropZoneElements;
+
+ qq.each(dropZones, function(idx, dropZone) {
+ setupDropzone(dropZone);
+ })
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (dropZones.length && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).css({display: 'block'});
+ });
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropZonesBeforeEnter && qq.FineUploader.prototype._leaving_document_out(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setupExtraDropzone: function(element) {
+ options.dropZoneElements.push(element);
+ setupDropzone(element);
+ },
+
+ removeDropzone: function(element) {
+ var i,
+ dzs = options.dropZoneElements;
+
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+qq.DragAndDrop.callbacks = function() {
+ return {
+ processingDroppedFiles: function() {},
+ processingDroppedFilesComplete: function(files) {},
+ dropError: function(code, errorSpecifics) {
+ qq.log("Drag & drop error code '" + code + " with these specifics: '" + errorSpecifics + "'", "error");
+ },
+ dropLog: function(message, level) {
+ qq.log(message, level);
+ }
+ }
+}
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file",
+ unsupportedBrowser: "Unrecoverable error - this browser does not permit file uploading of any kind."
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false,
+ prependFiles: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+
+ if (!qq.supportedFeatures.uploading || (this._options.cors.expected && !qq.supportedFeatures.uploadCors)) {
+ this._options.element.innerHTML = "" + this._options.messages.unsupportedBrowser + "
"
+ }
+ else {
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dropZoneElements = this._options.dragAndDrop.extraDropzones,
+ preventSelectFiles;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ dropZoneElements.push(this._find(this._options.element, 'drop'));
+ }
+
+ return new qq.DragAndDrop({
+ dropZoneElements: dropZoneElements,
+ hideDropZonesBeforeEnter: this._options.dragAndDrop.hideDropzones,
+ allowMultipleItems: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ processingDroppedFiles: function() {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ },
+ processingDroppedFilesComplete: function(files) {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ dropError: function(code, errorData) {
+ self._itemError(code, errorData);
+ },
+ dropLog: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type) {
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name) {
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ var onSuccessCallback = qq.bind(this._onSubmitDeleteSuccess, this, id);
+
+ qq.FineUploaderBasic.prototype._onSubmitDelete.call(this, id, onSuccessCallback);
+ },
+ _onSubmitDeleteSuccess: function(id) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ if (this._options.display.prependFiles) {
+ this._prependItem(item);
+ }
+ else {
+ this._listElement.appendChild(item);
+ }
+ this._filesInBatchAddedToUi += 1;
+
+ if (this._options.display.fileSizeOnSubmit && qq.supportedFeatures.ajaxUploading) {
+ this._displayFileSize(id);
+ }
+ },
+ _prependItem: function(item) {
+ var parentEl = this._listElement,
+ beforeEl = parentEl.firstChild;
+
+ if (this._totalFilesInBatch > 1 && this._filesInBatchAddedToUi > 0) {
+ beforeEl = qq(parentEl).children()[this._filesInBatchAddedToUi - 1].nextSibling;
+
+ }
+
+ parentEl.insertBefore(item, beforeEl);
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = item.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ },
+ _fileOrBlobRejected: function(id, name) {
+ this._totalFilesInBatch -= 1;
+ qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this, arguments);
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ this._totalFilesInBatch = items.length;
+ this._filesInBatchAddedToUi = 0;
+ qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this, arguments);
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, handlerImpl, api;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){},
+ onUuidChanged: function(id, newUuid){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.supportedFeatures.ajaxUploading) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, options.onUuidChanged, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, options.onUuidChanged, log);
+ }
+
+ function cancelSuccess(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ dequeue(id);
+ }
+
+
+ api = {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ handlerImpl.upload(id);
+ return true;
+ }
+
+ return false;
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ var cancelRetVal = handlerImpl.cancel(id);
+
+ if (qq.isPromise(cancelRetVal)) {
+ cancelRetVal.then(function() {
+ cancelSuccess(id);
+ });
+ }
+ else if (cancelRetVal !== false) {
+ cancelSuccess(id);
+ }
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ api.cancelAll();
+ queue = [];
+ handlerImpl.reset();
+ },
+ expunge: function(id) {
+ return handlerImpl.expunge(id);
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, onUuidChange, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ formHandlerInstanceId = qq.getUniqueId(),
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var iframeName = iframe.id,
+ fileId = getFileIdForIframeName(iframeName);
+
+ onloadCallbacks[uuids[fileId]] = callback;
+
+ detachLoadEvents[fileId] = qq(iframe).attach('load', function() {
+ if (inputs[fileId]) {
+ log("Received iframe load event for CORS upload request (iframe name " + iframeName + ")");
+
+ postMessageCallbackTimers[iframeName] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for iframe name " + iframeName;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(iframeName, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = parseResponse(getFileIdForIframeName(iframeName), message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ log("Handling response for iframe name " + iframeName);
+ clearTimeout(postMessageCallbackTimers[iframeName]);
+ delete postMessageCallbackTimers[iframeName];
+
+ detachLoadEvent(iframeName);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(iframeName);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(id, iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHtml = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHtml);
+ //plain text response may be wrapped in tag
+ if (innerHtml && innerHtml.match(/^ ');
+
+ iframe.setAttribute('id', iframeName);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+ function expungeFile(id) {
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(getIframeName(id));
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ }
+
+ function getFileIdForIframeName(iframeName) {
+ return iframeName.split("_")[0];
+ }
+
+ function getIframeName(fileId) {
+ return fileId + "_" + formHandlerInstanceId;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ formHandlerInstanceId = qq.getUniqueId();
+ },
+ expunge: function(id) {
+ return expungeFile(id);
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, api.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeFile(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeFile(id);
+ return true;
+ }
+
+ return false;
+ },
+
+ upload: function(id) {
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, api.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(id, iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, onUuidChange, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.supportedFeatures.chunking,
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.supportedFeatures.resume,
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+ options.onComplete(id, name, response, xhr);
+
+ if (fileState[id]) {
+ delete fileState[id].xhr;
+ }
+
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(id, xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+
+ if (response.newUuid !== undefined) {
+ log("Server requested UUID change from '" + fileState[id].uuid + "' to '" + response.newUuid + "'");
+ fileState[id].uuid = response.newUuid;
+ onUuidChanged(id, response.newUuid);
+ }
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(id, xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex) {
+ var currentChunkIndex;
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+
+ function handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex) {
+ var name = api.getName(id),
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part),
+ onResumeRetVal;
+
+ onResumeRetVal = options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume));
+ if (qq.isPromise(onResumeRetVal)) {
+ log("Waiting for onResume promise to be fulfilled for " + id);
+ onResumeRetVal.then(
+ function() {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ },
+ function() {
+ log("onResume promise fulfilled - failure indicated. Will not resume.")
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ );
+ }
+ else if (onResumeRetVal !== false) {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ }
+ else {
+ log("onResume callback returned false. Will not resume.");
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var firstChunkIndex = 0,
+ persistedChunkInfoForResume;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex);
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ uploadNextChunk(id);
+ }
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+ function expungeItem(id) {
+ var xhr = fileState[id].xhr;
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ }
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id, persistedChunkData,
+ uuid = qq.getUniqueId();
+
+ if (qq.isFile(fileOrBlobData)) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (qq.isBlob(fileOrBlobData.blob)) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ if (resumeEnabled) {
+ persistedChunkData = getPersistedChunkData(id);
+
+ if (persistedChunkData) {
+ uuid = persistedChunkData.uuid;
+ }
+ }
+
+ fileState[id].uuid = uuid;
+
+ return id;
+ },
+ getName: function(id){
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ expunge: function(id) {
+ return expungeItem(id);
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry) {
+ var name = this.getName(id);
+
+ if (this.isValid(id)) {
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ }
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, this.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeItem(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeItem(id);
+ return true;
+ }
+
+ return false;
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
diff --git a/ajax/libs/file-uploader/3.6.1/fineuploader.min.css b/ajax/libs/file-uploader/3.6.1/fineuploader.min.css
new file mode 100644
index 000000000..110e8fa52
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.1/fineuploader.min.css
@@ -0,0 +1 @@
+.qq-uploader{position:relative;width:100%;}.qq-upload-button{display:block;width:105px;padding:7px 0;text-align:center;background:#800;border-bottom:1px solid #DDD;color:#FFF;}.qq-upload-button-hover{background:#C00;}.qq-upload-button-focus{outline:1px dotted #000;}.qq-upload-drop-area,.qq-upload-extra-drop-area{position:absolute;top:0;left:0;width:100%;height:100%;min-height:30px;z-index:2;background:#FF9797;text-align:center;}.qq-upload-drop-area span{display:block;position:absolute;top:50%;width:100%;margin-top:-8px;font-size:16px;}.qq-upload-extra-drop-area{position:relative;margin-top:50px;font-size:16px;padding-top:30px;height:20px;min-height:40px;}.qq-upload-drop-area-active{background:#FF7171;}.qq-upload-list{margin:0;padding:0;list-style:none;}.qq-upload-list li{margin:0;padding:9px;line-height:15px;font-size:16px;background-color:#FFF0BD;}.qq-upload-file,.qq-upload-spinner,.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-failed-text,.qq-upload-finished,.qq-upload-delete{margin-right:12px;}.qq-upload-spinner{display:inline-block;background:url("loading.gif");width:15px;height:15px;vertical-align:text-bottom;}.qq-drop-processing{display:none;}.qq-drop-processing-spinner{display:inline-block;background:url("processing.gif");width:24px;height:24px;vertical-align:text-bottom;}.qq-upload-finished{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-retry,.qq-upload-delete{display:none;color:#000;}.qq-upload-cancel,.qq-upload-delete{color:#000;}.qq-upload-retryable .qq-upload-retry{display:inline;}.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-delete{font-size:12px;font-weight:normal;}.qq-upload-failed-text{display:none;font-style:italic;font-weight:bold;}.qq-upload-failed-icon{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-fail .qq-upload-failed-text{display:inline;}.qq-upload-retrying .qq-upload-failed-text{display:inline;color:#D60000;}.qq-upload-list li.qq-upload-success{background-color:#5DA30C;color:#FFF;}.qq-upload-list li.qq-upload-fail{background-color:#D60000;color:#FFF;}.qq-progress-bar{background:-moz-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,rgba(30,87,153,1)),color-stop(50%,rgba(41,137,216,1)),color-stop(51%,rgba(32,124,202,1)),color-stop(100%,rgba(125,185,232,1)));background:-webkit-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-o-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-ms-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:linear-gradient(to bottom,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);width:0;height:15px;border-radius:6px;margin-bottom:3px;display:none;}
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.1/fineuploader.min.js b/ajax/libs/file-uploader/3.6.1/fineuploader.min.js
new file mode 100644
index 000000000..b5eae56df
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.1/fineuploader.min.js
@@ -0,0 +1,15 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: 3.6.1
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!=null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return Object.prototype.toString.call(variable)==="[object Object]"};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isArray=function(variable){"use strict";return Object.prototype.toString.call(variable)==="[object Array]"};qq.isString=function(maybeString){"use strict";return Object.prototype.toString.call(maybeString)==="[object String]"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFile=function(maybeFile){"use strict";return window.File&&Object.prototype.toString.call(maybeFile)==="[object File]"};qq.isFileList=function(maybeFileList){return window.FileList&&Object.prototype.toString.call(maybeFileList)==="[object FileList]"};qq.isFileOrInput=function(maybeFileOrInput){"use strict";return qq.isFile(maybeFileOrInput)||qq.isInput(maybeFileOrInput)};qq.isInput=function(maybeInput){if(window.HTMLInputElement){if(Object.prototype.toString.call(maybeInput)==="[object HTMLInputElement]"){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}else if(maybeInput.tagName){if(maybeInput.tagName.toLowerCase()==="input"){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&Object.prototype.toString.call(maybeBlob)==="[object Blob]"};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice!==undefined||File.prototype.webkitSlice!==undefined||File.prototype.mozSlice!==undefined)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}});return first};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{defaultResponseError:"Upload failure reason unknown",sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"misc_data",paramNames:{name:"qqblobname"}},paste:{targetElement:null,defaultName:"pasted_image"},camera:{ios:false}};qq.extend(this._options,o,true);this._handleCameraAccess();this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData=this._createUploadDataTracker();this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}if(this._options.paste.targetElement){this._pasteHandler=this._createPasteHandler()}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader "+qq.version+"] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader "+qq.version+"] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},getNetUploads:function(){return this._netUploaded},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._netUploadedOrQueued++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset();this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData.reset();if(this._pasteHandler){this._pasteHandler.reset()}},addFiles:function(filesOrInputs,params,endpoint){var self=this,verifiedFilesOrInputs=[],fileOrInputIndex,fileOrInput,fileIndex;if(filesOrInputs){if(!qq.isFileList(filesOrInputs)){filesOrInputs=[].concat(filesOrInputs)}for(fileOrInputIndex=0;fileOrInputIndex=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||qq.supportedFeatures.deleteFileCors)},_onSubmitDelete:function(id,onSuccessCallback){if(this._isDeletePossible()){return this._handleCheckedCallback({name:"onSubmitDelete",callback:qq.bind(this._options.callbacks.onSubmitDelete,this,id),onSuccess:onSuccessCallback||qq.bind(this._deleteHandler.sendDelete,this,id,this.getUuid(id)),identifier:id})}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(id){this._uploadData.setStatus(id,qq.status.DELETING)},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);
+if(isError){this._uploadData.setStatus(id,qq.status.DELETE_FAILED);this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status,xhr)}else{this._uploadData.setStatus(id,qq.status.DELETED);this._netUploadedOrQueued--;this._netUploaded--;this._handler.expunge(id);this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){this._uploadData.setStatus(id,qq.status.UPLOADING)},_onInputChange:function(input){if(qq.supportedFeatures.ajaxUploading){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0&&this._netUploadedOrQueued+1>itemLimit){this._itemError("retryFailTooManyItems","");return false}this.log("Retrying upload for '"+fileName+"' (id: "+id+")...");this._filesInProgress.push(id);return true}else{this.log("'"+id+"' is not a valid file ID","error");return false}},_maybeParseAndSendUploadError:function(id,name,response,xhr){if(!response.success){if(xhr&&xhr.status!==200&&!response.error){this._options.callbacks.onError(id,name,"XHR returned response code "+xhr.status,xhr)}else{var errorReason=response.error?response.error:this._options.text.defaultResponseError;this._options.callbacks.onError(id,name,errorReason,xhr)}}},_prepareItemsForUpload:function(items,params,endpoint){var validationDescriptors=this._getValidationDescriptors(items);this._handleCheckedCallback({name:"onValidateBatch",callback:qq.bind(this._options.callbacks.onValidateBatch,this,validationDescriptors),onSuccess:qq.bind(this._onValidateBatchCallbackSuccess,this,validationDescriptors,items,params,endpoint),identifier:"batch validation"})},_upload:function(blobOrFileContainer,params,endpoint){var id=this._handler.add(blobOrFileContainer),name=this._handler.getName(id);this._uploadData.added(id);if(params){this.setParams(params,id)}if(endpoint){this.setEndpoint(endpoint,id)}this._handleCheckedCallback({name:"onSubmit",callback:qq.bind(this._options.callbacks.onSubmit,this,id,name),onSuccess:qq.bind(this._onSubmitCallbackSuccess,this,id,name),onFailure:qq.bind(this._fileOrBlobRejected,this,id,name),identifier:id})},_onSubmitCallbackSuccess:function(id,name){this._uploadData.setStatus(id,qq.status.SUBMITTED);this._onSubmit(id,name);this._options.callbacks.onSubmitted(id,name);if(this._options.autoUpload){if(!this._handler.upload(id)){this._uploadData.setStatus(id,qq.status.QUEUED)}}else{this._storeForLater(id)}},_storeForLater:function(id){this._storedIds.push(id)},_onValidateBatchCallbackSuccess:function(validationDescriptors,items,params,endpoint){var errorMessage,itemLimit=this._options.validation.itemLimit,proposedNetFilesUploadedOrQueued=this._netUploadedOrQueued+validationDescriptors.length;if(itemLimit===0||proposedNetFilesUploadedOrQueued<=itemLimit){if(items.length>0){this._handleCheckedCallback({name:"onValidate",callback:qq.bind(this._options.callbacks.onValidate,this,items[0]),onSuccess:qq.bind(this._onValidateCallbackSuccess,this,items,0,params,endpoint),onFailure:qq.bind(this._onValidateCallbackFailure,this,items,0,params,endpoint),identifier:"Item '"+items[0].name+"', size: "+items[0].size})}else{this._itemError("noFilesError","")}}else{errorMessage=this._options.messages.tooManyItemsError.replace(/\{netItems\}/g,proposedNetFilesUploadedOrQueued).replace(/\{itemLimit\}/g,itemLimit);this._batchError(errorMessage)}},_onValidateCallbackSuccess:function(items,index,params,endpoint){var nextIndex=index+1,validationDescriptor=this._getValidationDescriptor(items[index]),validItem=false;if(this._validateFileOrBlobData(items[index],validationDescriptor)){validItem=true;this._upload(items[index],params,endpoint)}this._maybeProcessNextItemAfterOnValidateCallback(validItem,items,nextIndex,params,endpoint)},_onValidateCallbackFailure:function(items,index,params,endpoint){var nextIndex=index+1;this._fileOrBlobRejected(undefined,items[0].name);this._maybeProcessNextItemAfterOnValidateCallback(false,items,nextIndex,params,endpoint)},_maybeProcessNextItemAfterOnValidateCallback:function(validItem,items,index,params,endpoint){var self=this;if(items.length>index){if(validItem||!this._options.validation.stopOnFirstInvalidFile){setTimeout(function(){var validationDescriptor=self._getValidationDescriptor(items[index]);self._handleCheckedCallback({name:"onValidate",callback:qq.bind(self._options.callbacks.onValidate,self,items[index]),onSuccess:qq.bind(self._onValidateCallbackSuccess,self,items,index,params,endpoint),onFailure:qq.bind(self._onValidateCallbackFailure,self,items,index,params,endpoint),identifier:"Item '"+validationDescriptor.name+"', size: "+validationDescriptor.size})},0)}}},_validateFileOrBlobData:function(item,validationDescriptor){var name=validationDescriptor.name,size=validationDescriptor.size,valid=true;if(this._options.callbacks.onValidate(validationDescriptor)===false){valid=false}if(qq.isFileOrInput(item)&&!this._isAllowedExtension(name)){this._itemError("typeError",name);valid=false}else if(size===0){this._itemError("emptyError",name);valid=false}else if(size&&this._options.validation.sizeLimit&&size>this._options.validation.sizeLimit){this._itemError("sizeError",name);valid=false}else if(size&&size999);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size!==undefined){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}},_handleCameraAccess:function(){if(this._options.camera.ios&&qq.ios()){this._options.multiple=false;if(this._options.validation.acceptFiles===null){this._options.validation.acceptFiles="image/*;capture=camera"}else{this._options.validation.acceptFiles+=",image/*;capture=camera"}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,droppedFiles=[],disposeSupport=new qq.DisposeSupport;options={dropZoneElements:[],hideDropZonesBeforeEnter:false,allowMultipleItems:true,classes:{dropActive:null},callbacks:new qq.DragAndDrop.callbacks};qq.extend(options,o,true);setupDragDrop();function uploadDroppedFiles(files){options.callbacks.dropLog("Grabbed "+files.length+" dropped files.");dz.dropDisabled(false);options.callbacks.processingDroppedFilesComplete(files)}function traverseFileTree(entry){var dirReader,i,parseEntryPromise=new qq.Promise;if(entry.isFile){entry.file(function(file){droppedFiles.push(file);parseEntryPromise.success()},function(fileError){options.callbacks.dropLog("Problem parsing '"+entry.fullPath+"'. FileError code "+fileError.code+".","error");parseEntryPromise.failure()})}else if(entry.isDirectory){dirReader=entry.createReader();dirReader.readEntries(function(entries){var entriesLeft=entries.length;for(i=0;i1&&!options.allowMultipleItems){options.callbacks.processingDroppedFilesComplete([]);options.callbacks.dropError("tooManyFilesError","");dz.dropDisabled(false);handleDataTransferPromise.failure()}else{droppedFiles=[];if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file",unsupportedBrowser:"Unrecoverable error - this browser does not permit file uploading of any kind."},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false,prependFiles:false},paste:{promptForName:false,namePromptMessage:"Please name this image"},showMessage:function(message){setTimeout(function(){window.alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=window.confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)},showPrompt:function(message,defaultValue){var promise=new qq.Promise,retVal=window.prompt(message,defaultValue);if(retVal!=null&&qq.trimStr(retVal).length>0){promise.success(retVal)}else{promise.failure("Undefined or invalid user-supplied value.")}return promise}},true);qq.extend(this._options,o,true);if(!qq.supportedFeatures.uploading||this._options.cors.expected&&!qq.supportedFeatures.uploadCors){this._options.element.innerHTML=""+this._options.messages.unsupportedBrowser+"
"}else{this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop();if(this._options.paste.targetElement&&this._options.paste.promptForName){this._setupPastePrompt()}this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0}};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop();this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dropZoneElements=this._options.dragAndDrop.extraDropzones,preventSelectFiles;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){dropZoneElements.push(this._find(this._options.element,"drop"))}return new qq.DragAndDrop({dropZoneElements:dropZoneElements,hideDropZonesBeforeEnter:this._options.dragAndDrop.hideDropzones,allowMultipleItems:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{processingDroppedFiles:function(){var input=self._button.getInput();qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)},processingDroppedFilesComplete:function(files){var input=self._button.getInput();qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles);if(files){self.addFiles(files)}},dropError:function(code,errorData){self._itemError(code,errorData)},dropLog:function(message,level){self.log(message,level)}}})},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){var item=this.getItemByFileId(id);if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}else{qq(item).addClass(this._classes.retryable);return false}},_onSubmitDelete:function(id){var onSuccessCallback=qq.bind(this._onSubmitDeleteSuccess,this,id);qq.FineUploaderBasic.prototype._onSubmitDelete.call(this,id,onSuccessCallback)},_onSubmitDeleteSuccess:function(id){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}if(this._options.display.prependFiles){this._prependItem(item)}else{this._listElement.appendChild(item)}this._filesInBatchAddedToUi+=1;if(this._options.display.fileSizeOnSubmit&&qq.supportedFeatures.ajaxUploading){this._displayFileSize(id)}},_prependItem:function(item){var parentEl=this._listElement,beforeEl=parentEl.firstChild;if(this._totalFilesInBatch>1&&this._filesInBatchAddedToUi>0){beforeEl=qq(parentEl).children()[this._filesInBatchAddedToUi-1].nextSibling}parentEl.insertBefore(item,beforeEl)},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=item.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)
+}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_itemError:function(code,name){var message=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(message)},_batchError:function(message){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments);this._options.showMessage(message)},_setupPastePrompt:function(){var self=this;this._options.callbacks.onPasteReceived=function(){var message=self._options.paste.namePromptMessage,defaultVal=self._options.paste.defaultName;return self._options.showPrompt(message,defaultVal)}},_fileOrBlobRejected:function(id,name){this._totalFilesInBatch-=1;qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this,arguments)},_prepareItemsForUpload:function(items,params,endpoint){this._totalFilesInBatch=items.length;this._filesInBatchAddedToUi=0;qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this,arguments)}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,handlerImpl,api;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){},onUuidChanged:function(id,newUuid){}};qq.extend(options,o);log=options.log;function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){var cancelRetVal=handlerImpl.cancel(id);if(qq.isPromise(cancelRetVal)){cancelRetVal.then(function(){cancelSuccess(id)})}else if(cancelRetVal!==false){cancelSuccess(id)}},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},reset:function(){log("Resetting upload handler");api.cancelAll();queue=[];handlerImpl.reset()},expunge:function(id){return handlerImpl.expunge(id)},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}};return api};qq.UploadHandlerForm=function(o,uploadCompleteCallback,onUuidChange,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},formHandlerInstanceId=qq.getUniqueId(),api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var iframeName=iframe.id,fileId=getFileIdForIframeName(iframeName);onloadCallbacks[uuids[fileId]]=callback;detachLoadEvents[fileId]=qq(iframe).attach("load",function(){if(inputs[fileId]){log("Received iframe load event for CORS upload request (iframe name "+iframeName+")");postMessageCallbackTimers[iframeName]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for iframe name "+iframeName;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(iframeName,function(message){log("Received the following window message: '"+message+"'");var response=parseResponse(getFileIdForIframeName(iframeName),message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){log("Handling response for iframe name "+iframeName);clearTimeout(postMessageCallbackTimers[iframeName]);delete postMessageCallbackTimers[iframeName];detachLoadEvent(iframeName);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(iframeName);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(id,iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHtml=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHtml);if(innerHtml&&innerHtml.match(/^ ');iframe.setAttribute("id",iframeName);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}function expungeFile(id){delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(getIframeName(id));if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}}function getFileIdForIframeName(iframeName){return iframeName.split("_")[0]}function getIframeName(fileId){return fileId+"_"+formHandlerInstanceId}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){if(api.isValid(id)){return inputs[id].value.replace(/.*(\/|\\)/,"")}else{log(id+" is not a valid item ID.","error")}},isValid:function(id){return inputs[id]!==undefined},reset:function(){inputs=[];uuids=[];detachLoadEvents={};formHandlerInstanceId=qq.getUniqueId()},expunge:function(id){return expungeFile(id)},getUuid:function(id){return uuids[id]},cancel:function(id){var onCancelRetVal=options.onCancel(id,api.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeFile(id)})}else if(onCancelRetVal!==false){expungeFile(id);return true}return false},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,api.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(id,iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove()}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,onUuidChange,logCallback){"use strict";var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.supportedFeatures.chunking,resumeEnabled=options.resume.enabled&&chunkFiles&&qq.supportedFeatures.resume,resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);if(fileState[id]){delete fileState[id].xhr}uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(id,xhr){var response;try{response=qq.parseJson(xhr.responseText);if(response.newUuid!==undefined){log("Server requested UUID change from '"+fileState[id].uuid+"' to '"+response.newUuid+"'");fileState[id].uuid=response.newUuid;onUuidChanged(id,response.newUuid)}}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(id,xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex){var currentChunkIndex;for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}uploadNextChunk(id)}function onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex);calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}function handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex){var name=api.getName(id),firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part),onResumeRetVal;onResumeRetVal=options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume));if(qq.isPromise(onResumeRetVal)){log("Waiting for onResume promise to be fulfilled for "+id);onResumeRetVal.then(function(){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)},function(){log("onResume promise fulfilled - failure indicated. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)})}else if(onResumeRetVal!==false){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)}else{log("onResume callback returned false. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}function handleFileChunkingUpload(id,retry){var firstChunkIndex=0,persistedChunkInfoForResume;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex)}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{uploadNextChunk(id)}}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}function expungeItem(id){var xhr=fileState[id].xhr;if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]}api={add:function(fileOrBlobData){var id,persistedChunkData,uuid=qq.getUniqueId();if(qq.isFile(fileOrBlobData)){id=fileState.push({file:fileOrBlobData})-1}else if(qq.isBlob(fileOrBlobData.blob)){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}if(resumeEnabled){persistedChunkData=getPersistedChunkData(id);if(persistedChunkData){uuid=persistedChunkData.uuid}}fileState[id].uuid=uuid;return id},getName:function(id){if(api.isValid(id)){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}}else{log(id+" is not a valid item ID.","error")}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},expunge:function(id){return expungeItem(id)},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);if(this.isValid(id)){options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}}},cancel:function(id){var onCancelRetVal=options.onCancel(id,this.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeItem(id)})}else if(onCancelRetVal!==false){expungeItem(id);return true}return false},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.1/iframe.xss.response.js b/ajax/libs/file-uploader/3.6.1/iframe.xss.response.js
new file mode 100644
index 000000000..e11fca1e5
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.1/iframe.xss.response.js
@@ -0,0 +1,6 @@
+(function() {
+ var match = /(\{.+\}).+/.exec(document.body.innerHTML);
+ if (match) {
+ parent.postMessage(match[1], '*');
+ }
+}());
diff --git a/ajax/libs/file-uploader/3.6.1/iframe.xss.response.min.js b/ajax/libs/file-uploader/3.6.1/iframe.xss.response.min.js
new file mode 100644
index 000000000..345cf3603
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.1/iframe.xss.response.min.js
@@ -0,0 +1 @@
+!function(){var match=/(\{.+\}).+/.exec(document.body.innerHTML);if(match){parent.postMessage(match[1],"*")}}();
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.1/loading.gif b/ajax/libs/file-uploader/3.6.1/loading.gif
new file mode 100644
index 000000000..6fba77609
Binary files /dev/null and b/ajax/libs/file-uploader/3.6.1/loading.gif differ
diff --git a/ajax/libs/file-uploader/3.6.1/processing.gif b/ajax/libs/file-uploader/3.6.1/processing.gif
new file mode 100644
index 000000000..7c99504e1
Binary files /dev/null and b/ajax/libs/file-uploader/3.6.1/processing.gif differ
diff --git a/ajax/libs/file-uploader/3.6.2/fineuploader-jquery.js b/ajax/libs/file-uploader/3.6.2/fineuploader-jquery.js
new file mode 100644
index 000000000..45f930107
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.2/fineuploader-jquery.js
@@ -0,0 +1,5039 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: -unstable-
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity != null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable && !variable.nodeType && Object.prototype.toString.call(variable) === '[object Object]';
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isArray = function(variable) {
+ "use strict";
+ return Object.prototype.toString.call(variable) === "[object Array]";
+}
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFile = function(maybeFile) {
+ "use strict";
+
+ return window.File && Object.prototype.toString.call(maybeFile) === '[object File]'
+};
+
+qq.isFileList = function(maybeFileList) {
+ return window.FileList && Object.prototype.toString.call(maybeFileList) === '[object FileList]'
+}
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+
+ return qq.isFile(maybeFileOrInput) || qq.isInput(maybeFileOrInput);
+};
+
+qq.isInput = function(maybeInput) {
+ if (window.HTMLInputElement) {
+ if (Object.prototype.toString.call(maybeInput) === '[object HTMLInputElement]') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ else if (maybeInput.tagName) {
+ if (maybeInput.tagName.toLowerCase() === 'input') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && Object.prototype.toString.call(maybeBlob) === '[object Blob]';
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice !== undefined || File.prototype.webkitSlice !== undefined || File.prototype.mozSlice !== undefined);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+
+ return first;
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+qq.ios = function() {
+ "use strict";
+ return navigator.userAgent.indexOf("iPad") !== -1
+ || navigator.userAgent.indexOf("iPod") !== -1
+ || navigator.userAgent.indexOf("iPhone") !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(objOrArray, callback) {
+ "use strict";
+ var keyOrIndex, retVal;
+ if (objOrArray) {
+ if (qq.isArray(objOrArray)) {
+ for (keyOrIndex = 0; keyOrIndex < objOrArray.length; keyOrIndex++) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ else {
+ for (keyOrIndex in objOrArray) {
+ if (Object.prototype.hasOwnProperty.call(objOrArray, keyOrIndex)) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+ }
+};
+
+//include any args that should be passed to the new function after the context arg
+qq.bind = function(oldFunc, context) {
+ if (qq.isFunction(oldFunc)) {
+ var args = Array.prototype.slice.call(arguments, 2);
+
+ return function() {
+ if (arguments.length) {
+ args = args.concat(Array.prototype.slice.call(arguments))
+ }
+ return oldFunc.apply(context, args);
+ };
+ }
+
+ throw new Error("first parameter must be a function!");
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ cookie;
+
+ qq.each(ca, function(idx, part) {
+ var cookiePart = part;
+ while (cookiePart.charAt(0)==' ') {
+ cookiePart = cookiePart.substring(1, cookiePart.length);
+ }
+
+ if (cookiePart.indexOf(nameEQ) === 0) {
+ cookie = cookiePart.substring(nameEQ.length, cookiePart.length);
+ return false;
+ }
+ });
+
+ return cookie;
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+qq.version="-unstable-";qq.supportedFeatures = (function() {
+ var supportsUploading,
+ supportsAjaxFileUploading,
+ supportsFolderDrop,
+ supportsChunking,
+ supportsResume,
+ supportsUploadViaPaste,
+ supportsUploadCors,
+ supportsDeleteFileCors;
+
+
+ function testSupportsFileInputElement() {
+ var supported = true,
+ tempInput;
+
+ try {
+ tempInput = document.createElement('input');
+ tempInput.type = 'file';
+ qq(tempInput).hide();
+
+ if(tempInput.disabled) {
+ supported = false;
+ }
+ }
+ catch(ex) {
+ supported = false;
+ }
+
+ return supported;
+ }
+
+ //only way to test for Filesystem API support since webkit does not expose the DataTransfer interface
+ function isChrome21OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[2][1-9]|Chrome\/[3-9][0-9]/) !== undefined;
+ }
+
+ //only way to test for complete Clipboard API support at this time
+ function isChrome14OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/) !== undefined;
+ }
+
+
+ supportsUploading = testSupportsFileInputElement();
+
+ supportsAjaxFileUploading = supportsUploading && qq.isXhrUploadSupported();
+
+ supportsFolderDrop = supportsAjaxFileUploading && isChrome21OrHigher();
+
+ supportsChunking = supportsAjaxFileUploading && qq.isFileChunkingSupported();
+
+ supportsResume = supportsAjaxFileUploading && supportsChunking && qq.areCookiesEnabled();
+
+ supportsUploadViaPaste = supportsAjaxFileUploading && isChrome14OrHigher();
+
+ supportsUploadCors = supportsUploading && (window.postMessage !== undefined || supportsAjaxFileUploading);
+
+ supportsDeleteFileCors = supportsAjaxFileUploading;
+
+
+ return {
+ uploading: supportsUploading,
+ ajaxUploading: supportsAjaxFileUploading,
+ fileDrop: supportsAjaxFileUploading, //NOTE: will also return true for touch-only devices. It's not currently possible to accurately test for touch-only devices
+ folderDrop: supportsFolderDrop,
+ chunking: supportsChunking,
+ resume: supportsResume,
+ uploadCustomHeaders: supportsAjaxFileUploading,
+ uploadNonMultipart: supportsAjaxFileUploading,
+ itemSizeValidation: supportsAjaxFileUploading,
+ uploadViaPaste: supportsUploadViaPaste,
+ progressBar: supportsAjaxFileUploading,
+ uploadCors: supportsUploadCors,
+ deleteFileCors: supportsDeleteFileCors,
+ canDetermineSize: supportsAjaxFileUploading
+ }
+
+}());
+/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallbacks = [],
+ failureCallbacks = [],
+ doneCallbacks = [],
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ if (onSuccess) {
+ successCallbacks.push(onSuccess);
+ }
+ if (onFailure) {
+ failureCallbacks.push(onFailure);
+ }
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+
+ return this;
+ },
+
+ done: function(callback) {
+ if (state === 0) {
+ doneCallbacks.push(callback);
+ }
+ else {
+ callback();
+ }
+
+ return this;
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallbacks.length) {
+ qq.each(successCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallbacks.length) {
+ qq.each(failureCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ }
+ };
+};
+
+qq.isPromise = function(maybePromise) {
+ return maybePromise && maybePromise.then && maybePromise.done;
+};/*globals qq*/
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ disposeSupport = new qq.DisposeSupport(),
+ options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input) {},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+
+ qq.extend(options, o);
+
+ // make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};qq.UploadData = function(uploaderProxy) {
+ var data = [],
+ byId = {},
+ byUuid = {},
+ byStatus = {},
+ api;
+
+ function getDataByIds(ids) {
+ if (qq.isArray(ids)) {
+ var entries = [];
+
+ qq.each(ids, function(idx, id) {
+ entries.push(data[byId[id]]);
+ });
+
+ return entries;
+ }
+
+ return data[byId[ids]];
+ }
+
+ function getDataByUuids(uuids) {
+ if (qq.isArray(uuids)) {
+ var entries = [];
+
+ qq.each(uuids, function(idx, uuid) {
+ entries.push(data[byUuid[uuid]]);
+ });
+
+ return entries;
+ }
+
+ return data[byUuid[uuids]];
+ }
+
+ function getDataByStatus(status) {
+ var statusResults = [],
+ statuses = [].concat(status);
+
+ qq.each(statuses, function(index, statusEnum) {
+ var statusResultIndexes = byStatus[statusEnum];
+
+ if (statusResultIndexes !== undefined) {
+ qq.each(statusResultIndexes, function(i, dataIndex) {
+ statusResults.push(data[dataIndex]);
+ });
+ }
+ });
+
+ return statusResults;
+ }
+
+ api = {
+ added: function(id) {
+ var uuid = uploaderProxy.getUuid(id),
+ name = uploaderProxy.getName(id),
+ size = uploaderProxy.getSize(id),
+ status = qq.status.SUBMITTING;
+
+ var index = data.push({
+ id: id,
+ name: name,
+ uuid: uuid,
+ size: size,
+ status: status
+ }) - 1;
+
+ byId[id] = index;
+
+ byUuid[uuid] = index;
+
+ if (byStatus[status] === undefined) {
+ byStatus[status] = [];
+ }
+ byStatus[status].push(index);
+
+ uploaderProxy.onStatusChange(id, undefined, status);
+ },
+
+ retrieve: function(optionalFilter) {
+ if (qq.isObject(optionalFilter) && data.length) {
+ if (optionalFilter.id !== undefined) {
+ return getDataByIds(optionalFilter.id);
+ }
+
+ else if (optionalFilter.uuid !== undefined) {
+ return getDataByUuids(optionalFilter.uuid);
+ }
+
+ else if (optionalFilter.status) {
+ return getDataByStatus(optionalFilter.status);
+ }
+ }
+ else {
+ return qq.extend([], data, true);
+ }
+ },
+
+ reset: function() {
+ data = [];
+ byId = {};
+ byUuid = {};
+ byStatus = {};
+ },
+
+ setStatus: function(id, newStatus) {
+ var dataIndex = byId[id],
+ oldStatus = data[dataIndex].status,
+ byStatusOldStatusIndex = qq.indexOf(byStatus[oldStatus], dataIndex);
+
+ byStatus[oldStatus].splice(byStatusOldStatusIndex, 1);
+
+ data[dataIndex].status = newStatus;
+
+ if (byStatus[newStatus] === undefined) {
+ byStatus[newStatus] = [];
+ }
+ byStatus[newStatus].push(dataIndex);
+
+ uploaderProxy.onStatusChange(id, oldStatus, newStatus);
+ },
+
+ uuidChanged: function(id, newUuid) {
+ var dataIndex = byId[id],
+ oldUuid = data[dataIndex].uuid;
+
+ data[dataIndex].uuid = newUuid;
+ byUuid[newUuid] = dataIndex;
+ delete byUuid[oldUuid];
+ }
+ };
+
+ return api;
+};
+
+qq.status = {
+ SUBMITTING: "submitting",
+ SUBMITTED: "submitted",
+ REJECTED: "rejected",
+ QUEUED: "queued",
+ CANCELED: "canceled",
+ UPLOADING: "uploading",
+ UPLOAD_RETRYING: "retrying upload",
+ UPLOAD_SUCCESSFUL: "upload successful",
+ UPLOAD_FAILED: "upload failed",
+ DELETE_FAILED: "delete failed",
+ DELETING: "deleting",
+ DELETED: "deleted"
+};qq.FineUploaderBasic = function(o) {
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true,
+ acceptFiles: null
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON, maybeXhr){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){},
+ onPasteReceived: function(blob) {},
+ onStatusChange: function(id, oldStatus, newStatus) {}
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ defaultResponseError: "Upload failure reason unknown",
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'misc_data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ },
+ camera: {
+ ios: false
+ }
+ };
+
+ qq.extend(this._options, o, true);
+
+ this._handleCameraAccess();
+
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData = this._createUploadDataTracker();
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader ' + qq.version + '] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader ' + qq.version + '] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function() {
+ return this._filesInProgress.length;
+ },
+ getNetUploads: function() {
+ return this._netUploaded;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netUploadedOrQueued++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData.reset();
+
+ if (this._pasteHandler) {
+ this._pasteHandler.reset();
+ }
+ },
+ addFiles: function(filesOrInputs, params, endpoint) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ fileOrInputIndex, fileOrInput, fileIndex;
+
+ if (filesOrInputs) {
+ if (!qq.isFileList(filesOrInputs)) {
+ filesOrInputs = [].concat(filesOrInputs);
+ }
+
+ for (fileOrInputIndex = 0; fileOrInputIndex < filesOrInputs.length; fileOrInputIndex+=1) {
+ fileOrInput = filesOrInputs[fileOrInputIndex];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ if (qq.isInput(fileOrInput) && qq.supportedFeatures.ajaxUploading) {
+ for (fileIndex = 0; fileIndex < fileOrInput.files.length; fileIndex++) {
+ verifiedFilesOrInputs.push(fileOrInput.files[fileIndex]);
+ }
+ }
+ else {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Received ' + verifiedFilesOrInputs.length + ' files or inputs.');
+ this._prepareItemsForUpload(verifiedFilesOrInputs, params, endpoint);
+ }
+ },
+ addBlobs: function(blobDataOrArray, params, endpoint) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._prepareItemsForUpload(verifiedBlobDataList, params, endpoint);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ doesExist: function(fileOrBlobId) {
+ return this._handler.isValid(fileOrBlobId);
+ },
+ getUploads: function(optionalFilter) {
+ return this._uploadData.retrieve(optionalFilter);
+ },
+ _handleCheckedCallback: function(details) {
+ var self = this,
+ callbackRetVal = details.callback();
+
+ if (qq.isPromise(callbackRetVal)) {
+ this.log(details.name + " - waiting for " + details.name + " promise to be fulfilled for " + details.identifier);
+ return callbackRetVal.then(
+ function(successParam) {
+ self.log(details.name + " promise success for " + details.identifier);
+ details.onSuccess(successParam);
+ },
+ function() {
+ if (details.onFailure) {
+ self.log(details.name + " promise failure for " + details.identifier);
+ details.onFailure();
+ }
+ else {
+ self.log(details.name + " promise failure for " + details.identifier);
+ }
+ });
+ }
+
+ if (callbackRetVal !== false) {
+ details.onSuccess(callbackRetVal);
+ }
+ else {
+ if (details.onFailure) {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Invoking failure callback.")
+ details.onFailure();
+ }
+ else {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Will not proceed.")
+ }
+ }
+
+ return callbackRetVal;
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.supportedFeatures.ajaxUploading,
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result, xhr);
+ },
+ onCancel: function(id, name) {
+ return self._handleCheckedCallback({
+ name: "onCancel",
+ callback: qq.bind(self._options.callbacks.onCancel, self, id, name),
+ onSuccess: qq.bind(self._onCancel, self, id, name),
+ identifier: id
+ });
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ onUuidChanged: function(id, newUuid) {
+ self._uploadData.uuidChanged(id, newUuid);
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ self._handleCheckedCallback({
+ name: "onPasteReceived",
+ callback: qq.bind(self._options.callbacks.onPasteReceived, self, blob),
+ onSuccess: qq.bind(self._handlePasteSuccess, self, blob),
+ identifier: "pasted image"
+ });
+ }
+ }
+ });
+ },
+ _createUploadDataTracker: function() {
+ var self = this;
+
+ return new qq.UploadData({
+ getName: function(id) {
+ return self.getName(id);
+ },
+ getUuid: function(id) {
+ return self.getUuid(id);
+ },
+ getSize: function(id) {
+ return self.getSize(id);
+ },
+ onStatusChange: function(id, oldStatus, newStatus) {
+ self._options.callbacks.onStatusChange(id, oldStatus, newStatus);
+ }
+ });
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total) {
+ //nothing to do yet in core uploader
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_FAILED);
+ this._netUploadedOrQueued--;
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_SUCCESSFUL);
+ this._netUploaded++;
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.CANCELED);
+
+ this._netUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected || qq.supportedFeatures.deleteFileCors));
+ },
+ _onSubmitDelete: function(id, onSuccessCallback) {
+ if (this._isDeletePossible()) {
+ return this._handleCheckedCallback({
+ name: "onSubmitDelete",
+ callback: qq.bind(this._options.callbacks.onSubmitDelete, this, id),
+ onSuccess: onSuccessCallback || qq.bind(this._deleteHandler.sendDelete, this, id, this.getUuid(id)),
+ identifier: id
+ });
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(id) {
+ this._uploadData.setStatus(id, qq.status.DELETING);
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this._uploadData.setStatus(id, qq.status.DELETE_FAILED);
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.DELETED);
+ this._netUploadedOrQueued--;
+ this._netUploaded--;
+ this._handler.expunge(id);
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.UPLOADING);
+ },
+ _onInputChange: function(input){
+ if (qq.supportedFeatures.ajaxUploading) {
+ this.addFiles(input.files);
+ }
+ else {
+ this.addFiles(input);
+ }
+
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems", "");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : this._options.text.defaultResponseError;
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ var validationDescriptors = this._getValidationDescriptors(items);
+
+ this._handleCheckedCallback({
+ name: "onValidateBatch",
+ callback: qq.bind(this._options.callbacks.onValidateBatch, this, validationDescriptors),
+ onSuccess: qq.bind(this._onValidateBatchCallbackSuccess, this, validationDescriptors, items, params, endpoint),
+ identifier: "batch validation"
+ });
+ },
+ _upload: function(blobOrFileContainer, params, endpoint) {
+ var id = this._handler.add(blobOrFileContainer),
+ name = this._handler.getName(id);
+
+ this._uploadData.added(id);
+
+ if (params) {
+ this.setParams(params, id);
+ }
+
+ if (endpoint) {
+ this.setEndpoint(endpoint, id);
+ }
+
+ this._handleCheckedCallback({
+ name: "onSubmit",
+ callback: qq.bind(this._options.callbacks.onSubmit, this, id, name),
+ onSuccess: qq.bind(this._onSubmitCallbackSuccess, this, id, name),
+ onFailure: qq.bind(this._fileOrBlobRejected, this, id, name),
+ identifier: id
+ });
+ },
+ _onSubmitCallbackSuccess: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.SUBMITTED);
+
+ this._onSubmit(id, name);
+ this._options.callbacks.onSubmitted(id, name);
+
+ if (this._options.autoUpload) {
+ if (!this._handler.upload(id)) {
+ this._uploadData.setStatus(id, qq.status.QUEUED);
+ }
+ }
+ else {
+ this._storeForLater(id);
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _onValidateBatchCallbackSuccess: function(validationDescriptors, items, params, endpoint) {
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netUploadedOrQueued + validationDescriptors.length;
+
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ if (items.length > 0) {
+ this._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(this._options.callbacks.onValidate, this, items[0]),
+ onSuccess: qq.bind(this._onValidateCallbackSuccess, this, items, 0, params, endpoint),
+ onFailure: qq.bind(this._onValidateCallbackFailure, this, items, 0, params, endpoint),
+ identifier: "Item '" + items[0].name + "', size: " + items[0].size
+ });
+ }
+ else {
+ this._itemError("noFilesError", "");
+ }
+ }
+ else {
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ },
+ _onValidateCallbackSuccess: function(items, index, params, endpoint) {
+ var nextIndex = index+1,
+ validationDescriptor = this._getValidationDescriptor(items[index]),
+ validItem = false;
+
+ if (this._validateFileOrBlobData(items[index], validationDescriptor)) {
+ validItem = true;
+ this._upload(items[index], params, endpoint);
+ }
+
+ this._maybeProcessNextItemAfterOnValidateCallback(validItem, items, nextIndex, params, endpoint);
+ },
+ _onValidateCallbackFailure: function(items, index, params, endpoint) {
+ var nextIndex = index+ 1;
+
+ this._fileOrBlobRejected(undefined, items[0].name);
+
+ this._maybeProcessNextItemAfterOnValidateCallback(false, items, nextIndex, params, endpoint);
+ },
+ _maybeProcessNextItemAfterOnValidateCallback: function(validItem, items, index, params, endpoint) {
+ var self = this;
+
+ if (items.length > index) {
+ if (validItem || !this._options.validation.stopOnFirstInvalidFile) {
+ //use setTimeout to prevent a stack overflow with a large number of files in the batch & non-promissory callbacks
+ setTimeout(function() {
+ var validationDescriptor = self._getValidationDescriptor(items[index]);
+
+ self._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(self._options.callbacks.onValidate, self, items[index]),
+ onSuccess: qq.bind(self._onValidateCallbackSuccess, self, items, index, params, endpoint),
+ onFailure: qq.bind(self._onValidateCallbackFailure, self, items, index, params, endpoint),
+ identifier: "Item '" + validationDescriptor.name + "', size: " + validationDescriptor.size
+ });
+ }, 0);
+ }
+ }
+ },
+ _validateFileOrBlobData: function(item, validationDescriptor) {
+ var name = validationDescriptor.name,
+ size = validationDescriptor.size,
+ valid = true;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ valid = false;
+ }
+
+ if (qq.isFileOrInput(item) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ valid = false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ valid = false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ valid = false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ valid = false;
+ }
+
+ if (!valid) {
+ this._fileOrBlobRejected(undefined, name);
+ }
+
+ return valid;
+ },
+ _fileOrBlobRejected: function(id, name) {
+ if (id !== undefined) {
+ this._uploadData.setStatus(id, qq.status.REJECTED);
+ }
+ },
+ _itemError: function(code, nameOrNames) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ names = [].concat(nameOrNames),
+ name = names[0],
+ extensionsForMessage, placeholderMatch;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ placeholderMatch = message.match(/(\{\w+\})/g);
+ if (placeholderMatch !== null) {
+ qq.each(placeholderMatch, function(idx, placeholder) {
+ r(placeholder, names[idx]);
+ });
+ }
+
+ this._options.callbacks.onError(null, name, message, undefined);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message, undefined);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1000;
+ i++;
+ } while (bytes > 999);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size !== undefined) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ },
+ _handleCameraAccess: function() {
+ if (this._options.camera.ios && qq.ios()) {
+ this._options.multiple = false;
+
+ if (this._options.validation.acceptFiles === null) {
+ this._options.validation.acceptFiles = "image/*;capture=camera";
+ }
+ else {
+ this._options.validation.acceptFiles += ",image/*;capture=camera";
+ }
+ }
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz,
+ droppedFiles = [],
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropZoneElements: [],
+ hideDropZonesBeforeEnter: false,
+ allowMultipleItems: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: new qq.DragAndDrop.callbacks()
+ };
+
+ qq.extend(options, o, true);
+
+ setupDragDrop();
+
+ function uploadDroppedFiles(files) {
+ options.callbacks.dropLog('Grabbed ' + files.length + " dropped files.");
+ dz.dropDisabled(false);
+ options.callbacks.processingDroppedFilesComplete(files);
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i,
+ parseEntryPromise = new qq.Promise();
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ droppedFiles.push(file);
+ parseEntryPromise.success();
+ },
+ function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+ else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ var entriesLeft = entries.length;
+
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]).done(function() {
+ entriesLeft-=1;
+
+ if (entriesLeft === 0) {
+ parseEntryPromise.success();
+ }
+ });
+ }
+
+ if (!entries.length) {
+ parseEntryPromise.success();
+ }
+ }, function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+
+ return parseEntryPromise;
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry,
+ pendingFolderPromises = [],
+ handleDataTransferPromise = new qq.Promise();
+
+ options.callbacks.processingDroppedFiles();
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.allowMultipleItems) {
+ options.callbacks.processingDroppedFilesComplete([]);
+ options.callbacks.dropError('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ handleDataTransferPromise.failure();
+ }
+ else {
+ droppedFiles = [];
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ }
+
+ else {
+ pendingFolderPromises.push(traverseFileTree(entry).done(function() {
+ pendingFolderPromises.pop();
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }));
+ }
+ }
+ }
+ }
+ else {
+ droppedFiles = dataTransfer.files;
+ }
+
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }
+
+ return handleDataTransferPromise;
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer).done(function() {
+ uploadDroppedFiles(droppedFiles);
+ });
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ var dropZones = options.dropZoneElements;
+
+ qq.each(dropZones, function(idx, dropZone) {
+ setupDropzone(dropZone);
+ })
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (dropZones.length && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).css({display: 'block'});
+ });
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropZonesBeforeEnter && qq.FineUploader.prototype._leaving_document_out(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setupExtraDropzone: function(element) {
+ options.dropZoneElements.push(element);
+ setupDropzone(element);
+ },
+
+ removeDropzone: function(element) {
+ var i,
+ dzs = options.dropZoneElements;
+
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+qq.DragAndDrop.callbacks = function() {
+ return {
+ processingDroppedFiles: function() {},
+ processingDroppedFilesComplete: function(files) {},
+ dropError: function(code, errorSpecifics) {
+ qq.log("Drag & drop error code '" + code + " with these specifics: '" + errorSpecifics + "'", "error");
+ },
+ dropLog: function(message, level) {
+ qq.log(message, level);
+ }
+ }
+}
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file",
+ unsupportedBrowser: "Unrecoverable error - this browser does not permit file uploading of any kind."
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false,
+ prependFiles: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+
+ if (!qq.supportedFeatures.uploading || (this._options.cors.expected && !qq.supportedFeatures.uploadCors)) {
+ this._options.element.innerHTML = "" + this._options.messages.unsupportedBrowser + "
"
+ }
+ else {
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dropZoneElements = this._options.dragAndDrop.extraDropzones,
+ preventSelectFiles;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ dropZoneElements.push(this._find(this._options.element, 'drop'));
+ }
+
+ return new qq.DragAndDrop({
+ dropZoneElements: dropZoneElements,
+ hideDropZonesBeforeEnter: this._options.dragAndDrop.hideDropzones,
+ allowMultipleItems: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ processingDroppedFiles: function() {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ },
+ processingDroppedFilesComplete: function(files) {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ dropError: function(code, errorData) {
+ self._itemError(code, errorData);
+ },
+ dropLog: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type) {
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name) {
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ var onSuccessCallback = qq.bind(this._onSubmitDeleteSuccess, this, id);
+
+ qq.FineUploaderBasic.prototype._onSubmitDelete.call(this, id, onSuccessCallback);
+ },
+ _onSubmitDeleteSuccess: function(id) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ if (this._options.display.prependFiles) {
+ this._prependItem(item);
+ }
+ else {
+ this._listElement.appendChild(item);
+ }
+ this._filesInBatchAddedToUi += 1;
+
+ if (this._options.display.fileSizeOnSubmit && qq.supportedFeatures.ajaxUploading) {
+ this._displayFileSize(id);
+ }
+ },
+ _prependItem: function(item) {
+ var parentEl = this._listElement,
+ beforeEl = parentEl.firstChild;
+
+ if (this._totalFilesInBatch > 1 && this._filesInBatchAddedToUi > 0) {
+ beforeEl = qq(parentEl).children()[this._filesInBatchAddedToUi - 1].nextSibling;
+
+ }
+
+ parentEl.insertBefore(item, beforeEl);
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = item.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ },
+ _fileOrBlobRejected: function(id, name) {
+ this._totalFilesInBatch -= 1;
+ qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this, arguments);
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ this._totalFilesInBatch = items.length;
+ this._filesInBatchAddedToUi = 0;
+ qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this, arguments);
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, handlerImpl, api;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){},
+ onUuidChanged: function(id, newUuid){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.supportedFeatures.ajaxUploading) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, options.onUuidChanged, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, options.onUuidChanged, log);
+ }
+
+ function cancelSuccess(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ dequeue(id);
+ }
+
+
+ api = {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ handlerImpl.upload(id);
+ return true;
+ }
+
+ return false;
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ var cancelRetVal = handlerImpl.cancel(id);
+
+ if (qq.isPromise(cancelRetVal)) {
+ cancelRetVal.then(function() {
+ cancelSuccess(id);
+ });
+ }
+ else if (cancelRetVal !== false) {
+ cancelSuccess(id);
+ }
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ api.cancelAll();
+ queue = [];
+ handlerImpl.reset();
+ },
+ expunge: function(id) {
+ return handlerImpl.expunge(id);
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, onUuidChange, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ formHandlerInstanceId = qq.getUniqueId(),
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var iframeName = iframe.id,
+ fileId = getFileIdForIframeName(iframeName);
+
+ onloadCallbacks[uuids[fileId]] = callback;
+
+ detachLoadEvents[fileId] = qq(iframe).attach('load', function() {
+ if (inputs[fileId]) {
+ log("Received iframe load event for CORS upload request (iframe name " + iframeName + ")");
+
+ postMessageCallbackTimers[iframeName] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for iframe name " + iframeName;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(iframeName, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = parseResponse(getFileIdForIframeName(iframeName), message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ log("Handling response for iframe name " + iframeName);
+ clearTimeout(postMessageCallbackTimers[iframeName]);
+ delete postMessageCallbackTimers[iframeName];
+
+ detachLoadEvent(iframeName);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(iframeName);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(id, iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHtml = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHtml);
+ //plain text response may be wrapped in tag
+ if (innerHtml && innerHtml.match(/^ ');
+
+ iframe.setAttribute('id', iframeName);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+ function expungeFile(id) {
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(getIframeName(id));
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ }
+
+ function getFileIdForIframeName(iframeName) {
+ return iframeName.split("_")[0];
+ }
+
+ function getIframeName(fileId) {
+ return fileId + "_" + formHandlerInstanceId;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ formHandlerInstanceId = qq.getUniqueId();
+ },
+ expunge: function(id) {
+ return expungeFile(id);
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, api.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeFile(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeFile(id);
+ return true;
+ }
+
+ return false;
+ },
+
+ upload: function(id) {
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, api.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(id, iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, onUuidChange, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.supportedFeatures.chunking,
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.supportedFeatures.resume,
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+ options.onComplete(id, name, response, xhr);
+
+ if (fileState[id]) {
+ delete fileState[id].xhr;
+ }
+
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(id, xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+
+ if (response.newUuid !== undefined) {
+ log("Server requested UUID change from '" + fileState[id].uuid + "' to '" + response.newUuid + "'");
+ fileState[id].uuid = response.newUuid;
+ onUuidChanged(id, response.newUuid);
+ }
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(id, xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex) {
+ var currentChunkIndex;
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+
+ function handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex) {
+ var name = api.getName(id),
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part),
+ onResumeRetVal;
+
+ onResumeRetVal = options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume));
+ if (qq.isPromise(onResumeRetVal)) {
+ log("Waiting for onResume promise to be fulfilled for " + id);
+ onResumeRetVal.then(
+ function() {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ },
+ function() {
+ log("onResume promise fulfilled - failure indicated. Will not resume.")
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ );
+ }
+ else if (onResumeRetVal !== false) {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ }
+ else {
+ log("onResume callback returned false. Will not resume.");
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var firstChunkIndex = 0,
+ persistedChunkInfoForResume;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex);
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ uploadNextChunk(id);
+ }
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+ function expungeItem(id) {
+ var xhr = fileState[id].xhr;
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ }
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id, persistedChunkData,
+ uuid = qq.getUniqueId();
+
+ if (qq.isFile(fileOrBlobData)) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (qq.isBlob(fileOrBlobData.blob)) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ if (resumeEnabled) {
+ persistedChunkData = getPersistedChunkData(id);
+
+ if (persistedChunkData) {
+ uuid = persistedChunkData.uuid;
+ }
+ }
+
+ fileState[id].uuid = uuid;
+
+ return id;
+ },
+ getName: function(id){
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ expunge: function(id) {
+ return expungeItem(id);
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry) {
+ var name = this.getName(id);
+
+ if (this.isValid(id)) {
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ }
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, this.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeItem(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeItem(id);
+ return true;
+ }
+
+ return false;
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var uploader, $el, init, dataStore, pluginOption, pluginOptions, addCallbacks, transformVariables, isValidCommand,
+ delegateCommand;
+
+ pluginOptions = ['uploaderType'];
+
+ init = function (options) {
+ if (options) {
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+
+ if (pluginOption('uploaderType') === 'basic') {
+ uploader(new qq.FineUploaderBasic(xformedOpts));
+ }
+ else {
+ uploader(new qq.FineUploader(xformedOpts));
+ }
+ }
+
+ return $el;
+ };
+
+ dataStore = function(key, val) {
+ var data = $el.data('fineuploader');
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data('fineuploader', data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ //the underlying Fine Uploader instance is stored in jQuery's data stored, associated with the element
+ // tied to this instance of the plug-in
+ uploader = function(instanceToStore) {
+ return dataStore('uploader', instanceToStore);
+ };
+
+ pluginOption = function(option, optionVal) {
+ return dataStore(option, optionVal);
+ };
+
+ //implement all callbacks defined in Fine Uploader as functions that trigger appropriately names events and
+ // return the result of executing the bound handler back to Fine Uploader
+ addCallbacks = function(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {},
+ uploaderInst = new qq.FineUploaderBasic();
+
+ $.each(uploaderInst._options.callbacks, function(prop, func) {
+ var name, $callbackEl;
+
+ name = /^on(\w+)/.exec(prop)[1];
+ name = name.substring(0, 1).toLowerCase() + name.substring(1);
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var args = Array.prototype.slice.call(arguments);
+
+ return $callbackEl.triggerHandler(name, args);
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ transformVariables = function(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ if (source.uploaderType !== 'basic') {
+ xformed = { element : $el[0] };
+ }
+ else {
+ xformed = {};
+ }
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if ($.inArray(prop, pluginOptions) >= 0) {
+ pluginOption(prop, val);
+ }
+ else if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ isValidCommand = function(command) {
+ return $.type(command) === "string" &&
+ !command.match(/^_/) && //enforce private methods convention
+ uploader()[command] !== undefined;
+ };
+
+ //assuming we have already verified that this is a valid command, call the associated function in the underlying
+ // Fine Uploader instance (passing along the arguments from the caller) and return the result of the call back to the caller
+ delegateCommand = function(command) {
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
+
+ transformVariables(origArgs, xformedArgs);
+
+ return uploader()[command].apply(uploader(), xformedArgs);
+ };
+
+ $.fn.fineUploader = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (uploader() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error('Method ' + optionsOrCommand + ' does not exist on jQuery.fineUploader');
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
+/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var rootDataKey = "fineUploaderDnd",
+ $el;
+
+ function init (options) {
+ if (!options) {
+ options = {};
+ }
+
+ options.dropZoneElements = [$el];
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+ dnd(new qq.DragAndDrop(xformedOpts));
+
+ return $el;
+ };
+
+ function dataStore(key, val) {
+ var data = $el.data(rootDataKey);
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data(rootDataKey, data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ function dnd(instanceToStore) {
+ return dataStore('dndInstance', instanceToStore);
+ };
+
+ function addCallbacks(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {},
+ dndInst = new qq.FineUploaderBasic();
+
+ $.each(new qq.DragAndDrop.callbacks(), function(prop, func) {
+ var name = prop,
+ $callbackEl;
+
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var args = Array.prototype.slice.call(arguments),
+ jqueryHandlerResult = $callbackEl.triggerHandler(name, args);
+
+ return jqueryHandlerResult;
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ function transformVariables(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ xformed = {};
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ function isValidCommand(command) {
+ return $.type(command) === "string" &&
+ command === "dispose" &&
+ dnd()[command] !== undefined;
+ };
+
+ function delegateCommand(command) {
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
+ transformVariables(origArgs, xformedArgs);
+ return dnd()[command].apply(dnd(), xformedArgs);
+ };
+
+ $.fn.fineUploaderDnd = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (dnd() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error("Method " + optionsOrCommand + " does not exist in Fine Uploader's DnD module.");
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
diff --git a/ajax/libs/file-uploader/3.6.2/fineuploader-jquery.min.js b/ajax/libs/file-uploader/3.6.2/fineuploader-jquery.min.js
new file mode 100644
index 000000000..f224e223a
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.2/fineuploader-jquery.min.js
@@ -0,0 +1,16 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: 3.6.2
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!=null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable&&!variable.nodeType&&Object.prototype.toString.call(variable)==="[object Object]"};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isArray=function(variable){"use strict";return Object.prototype.toString.call(variable)==="[object Array]"};qq.isString=function(maybeString){"use strict";return Object.prototype.toString.call(maybeString)==="[object String]"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFile=function(maybeFile){"use strict";return window.File&&Object.prototype.toString.call(maybeFile)==="[object File]"};qq.isFileList=function(maybeFileList){return window.FileList&&Object.prototype.toString.call(maybeFileList)==="[object FileList]"};qq.isFileOrInput=function(maybeFileOrInput){"use strict";return qq.isFile(maybeFileOrInput)||qq.isInput(maybeFileOrInput)};qq.isInput=function(maybeInput){if(window.HTMLInputElement){if(Object.prototype.toString.call(maybeInput)==="[object HTMLInputElement]"){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}else if(maybeInput.tagName){if(maybeInput.tagName.toLowerCase()==="input"){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&Object.prototype.toString.call(maybeBlob)==="[object Blob]"};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice!==undefined||File.prototype.webkitSlice!==undefined||File.prototype.mozSlice!==undefined)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}});return first};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{defaultResponseError:"Upload failure reason unknown",sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"misc_data",paramNames:{name:"qqblobname"}},paste:{targetElement:null,defaultName:"pasted_image"},camera:{ios:false}};qq.extend(this._options,o,true);this._handleCameraAccess();this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData=this._createUploadDataTracker();this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}if(this._options.paste.targetElement){this._pasteHandler=this._createPasteHandler()}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader "+qq.version+"] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader "+qq.version+"] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},getNetUploads:function(){return this._netUploaded},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._netUploadedOrQueued++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset();this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData.reset();if(this._pasteHandler){this._pasteHandler.reset()}},addFiles:function(filesOrInputs,params,endpoint){var self=this,verifiedFilesOrInputs=[],fileOrInputIndex,fileOrInput,fileIndex;if(filesOrInputs){if(!qq.isFileList(filesOrInputs)){filesOrInputs=[].concat(filesOrInputs)}for(fileOrInputIndex=0;fileOrInputIndex=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||qq.supportedFeatures.deleteFileCors)},_onSubmitDelete:function(id,onSuccessCallback){if(this._isDeletePossible()){return this._handleCheckedCallback({name:"onSubmitDelete",callback:qq.bind(this._options.callbacks.onSubmitDelete,this,id),onSuccess:onSuccessCallback||qq.bind(this._deleteHandler.sendDelete,this,id,this.getUuid(id)),identifier:id})}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(id){this._uploadData.setStatus(id,qq.status.DELETING)},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);
+if(isError){this._uploadData.setStatus(id,qq.status.DELETE_FAILED);this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status,xhr)}else{this._uploadData.setStatus(id,qq.status.DELETED);this._netUploadedOrQueued--;this._netUploaded--;this._handler.expunge(id);this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){this._uploadData.setStatus(id,qq.status.UPLOADING)},_onInputChange:function(input){if(qq.supportedFeatures.ajaxUploading){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0&&this._netUploadedOrQueued+1>itemLimit){this._itemError("retryFailTooManyItems","");return false}this.log("Retrying upload for '"+fileName+"' (id: "+id+")...");this._filesInProgress.push(id);return true}else{this.log("'"+id+"' is not a valid file ID","error");return false}},_maybeParseAndSendUploadError:function(id,name,response,xhr){if(!response.success){if(xhr&&xhr.status!==200&&!response.error){this._options.callbacks.onError(id,name,"XHR returned response code "+xhr.status,xhr)}else{var errorReason=response.error?response.error:this._options.text.defaultResponseError;this._options.callbacks.onError(id,name,errorReason,xhr)}}},_prepareItemsForUpload:function(items,params,endpoint){var validationDescriptors=this._getValidationDescriptors(items);this._handleCheckedCallback({name:"onValidateBatch",callback:qq.bind(this._options.callbacks.onValidateBatch,this,validationDescriptors),onSuccess:qq.bind(this._onValidateBatchCallbackSuccess,this,validationDescriptors,items,params,endpoint),identifier:"batch validation"})},_upload:function(blobOrFileContainer,params,endpoint){var id=this._handler.add(blobOrFileContainer),name=this._handler.getName(id);this._uploadData.added(id);if(params){this.setParams(params,id)}if(endpoint){this.setEndpoint(endpoint,id)}this._handleCheckedCallback({name:"onSubmit",callback:qq.bind(this._options.callbacks.onSubmit,this,id,name),onSuccess:qq.bind(this._onSubmitCallbackSuccess,this,id,name),onFailure:qq.bind(this._fileOrBlobRejected,this,id,name),identifier:id})},_onSubmitCallbackSuccess:function(id,name){this._uploadData.setStatus(id,qq.status.SUBMITTED);this._onSubmit(id,name);this._options.callbacks.onSubmitted(id,name);if(this._options.autoUpload){if(!this._handler.upload(id)){this._uploadData.setStatus(id,qq.status.QUEUED)}}else{this._storeForLater(id)}},_storeForLater:function(id){this._storedIds.push(id)},_onValidateBatchCallbackSuccess:function(validationDescriptors,items,params,endpoint){var errorMessage,itemLimit=this._options.validation.itemLimit,proposedNetFilesUploadedOrQueued=this._netUploadedOrQueued+validationDescriptors.length;if(itemLimit===0||proposedNetFilesUploadedOrQueued<=itemLimit){if(items.length>0){this._handleCheckedCallback({name:"onValidate",callback:qq.bind(this._options.callbacks.onValidate,this,items[0]),onSuccess:qq.bind(this._onValidateCallbackSuccess,this,items,0,params,endpoint),onFailure:qq.bind(this._onValidateCallbackFailure,this,items,0,params,endpoint),identifier:"Item '"+items[0].name+"', size: "+items[0].size})}else{this._itemError("noFilesError","")}}else{errorMessage=this._options.messages.tooManyItemsError.replace(/\{netItems\}/g,proposedNetFilesUploadedOrQueued).replace(/\{itemLimit\}/g,itemLimit);this._batchError(errorMessage)}},_onValidateCallbackSuccess:function(items,index,params,endpoint){var nextIndex=index+1,validationDescriptor=this._getValidationDescriptor(items[index]),validItem=false;if(this._validateFileOrBlobData(items[index],validationDescriptor)){validItem=true;this._upload(items[index],params,endpoint)}this._maybeProcessNextItemAfterOnValidateCallback(validItem,items,nextIndex,params,endpoint)},_onValidateCallbackFailure:function(items,index,params,endpoint){var nextIndex=index+1;this._fileOrBlobRejected(undefined,items[0].name);this._maybeProcessNextItemAfterOnValidateCallback(false,items,nextIndex,params,endpoint)},_maybeProcessNextItemAfterOnValidateCallback:function(validItem,items,index,params,endpoint){var self=this;if(items.length>index){if(validItem||!this._options.validation.stopOnFirstInvalidFile){setTimeout(function(){var validationDescriptor=self._getValidationDescriptor(items[index]);self._handleCheckedCallback({name:"onValidate",callback:qq.bind(self._options.callbacks.onValidate,self,items[index]),onSuccess:qq.bind(self._onValidateCallbackSuccess,self,items,index,params,endpoint),onFailure:qq.bind(self._onValidateCallbackFailure,self,items,index,params,endpoint),identifier:"Item '"+validationDescriptor.name+"', size: "+validationDescriptor.size})},0)}}},_validateFileOrBlobData:function(item,validationDescriptor){var name=validationDescriptor.name,size=validationDescriptor.size,valid=true;if(this._options.callbacks.onValidate(validationDescriptor)===false){valid=false}if(qq.isFileOrInput(item)&&!this._isAllowedExtension(name)){this._itemError("typeError",name);valid=false}else if(size===0){this._itemError("emptyError",name);valid=false}else if(size&&this._options.validation.sizeLimit&&size>this._options.validation.sizeLimit){this._itemError("sizeError",name);valid=false}else if(size&&size999);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size!==undefined){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}},_handleCameraAccess:function(){if(this._options.camera.ios&&qq.ios()){this._options.multiple=false;if(this._options.validation.acceptFiles===null){this._options.validation.acceptFiles="image/*;capture=camera"}else{this._options.validation.acceptFiles+=",image/*;capture=camera"}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,droppedFiles=[],disposeSupport=new qq.DisposeSupport;options={dropZoneElements:[],hideDropZonesBeforeEnter:false,allowMultipleItems:true,classes:{dropActive:null},callbacks:new qq.DragAndDrop.callbacks};qq.extend(options,o,true);setupDragDrop();function uploadDroppedFiles(files){options.callbacks.dropLog("Grabbed "+files.length+" dropped files.");dz.dropDisabled(false);options.callbacks.processingDroppedFilesComplete(files)}function traverseFileTree(entry){var dirReader,i,parseEntryPromise=new qq.Promise;if(entry.isFile){entry.file(function(file){droppedFiles.push(file);parseEntryPromise.success()},function(fileError){options.callbacks.dropLog("Problem parsing '"+entry.fullPath+"'. FileError code "+fileError.code+".","error");parseEntryPromise.failure()})}else if(entry.isDirectory){dirReader=entry.createReader();dirReader.readEntries(function(entries){var entriesLeft=entries.length;for(i=0;i1&&!options.allowMultipleItems){options.callbacks.processingDroppedFilesComplete([]);options.callbacks.dropError("tooManyFilesError","");dz.dropDisabled(false);handleDataTransferPromise.failure()}else{droppedFiles=[];if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file",unsupportedBrowser:"Unrecoverable error - this browser does not permit file uploading of any kind."},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false,prependFiles:false},paste:{promptForName:false,namePromptMessage:"Please name this image"},showMessage:function(message){setTimeout(function(){window.alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=window.confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)},showPrompt:function(message,defaultValue){var promise=new qq.Promise,retVal=window.prompt(message,defaultValue);if(retVal!=null&&qq.trimStr(retVal).length>0){promise.success(retVal)}else{promise.failure("Undefined or invalid user-supplied value.")}return promise}},true);qq.extend(this._options,o,true);if(!qq.supportedFeatures.uploading||this._options.cors.expected&&!qq.supportedFeatures.uploadCors){this._options.element.innerHTML=""+this._options.messages.unsupportedBrowser+"
"}else{this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop();if(this._options.paste.targetElement&&this._options.paste.promptForName){this._setupPastePrompt()}this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0}};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop();this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dropZoneElements=this._options.dragAndDrop.extraDropzones,preventSelectFiles;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){dropZoneElements.push(this._find(this._options.element,"drop"))}return new qq.DragAndDrop({dropZoneElements:dropZoneElements,hideDropZonesBeforeEnter:this._options.dragAndDrop.hideDropzones,allowMultipleItems:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{processingDroppedFiles:function(){var input=self._button.getInput();qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)},processingDroppedFilesComplete:function(files){var input=self._button.getInput();qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles);if(files){self.addFiles(files)}},dropError:function(code,errorData){self._itemError(code,errorData)},dropLog:function(message,level){self.log(message,level)}}})},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){var item=this.getItemByFileId(id);if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}else{qq(item).addClass(this._classes.retryable);return false}},_onSubmitDelete:function(id){var onSuccessCallback=qq.bind(this._onSubmitDeleteSuccess,this,id);qq.FineUploaderBasic.prototype._onSubmitDelete.call(this,id,onSuccessCallback)},_onSubmitDeleteSuccess:function(id){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}if(this._options.display.prependFiles){this._prependItem(item)}else{this._listElement.appendChild(item)}this._filesInBatchAddedToUi+=1;if(this._options.display.fileSizeOnSubmit&&qq.supportedFeatures.ajaxUploading){this._displayFileSize(id)}},_prependItem:function(item){var parentEl=this._listElement,beforeEl=parentEl.firstChild;if(this._totalFilesInBatch>1&&this._filesInBatchAddedToUi>0){beforeEl=qq(parentEl).children()[this._filesInBatchAddedToUi-1].nextSibling}parentEl.insertBefore(item,beforeEl)},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=item.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)
+}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_itemError:function(code,name){var message=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(message)},_batchError:function(message){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments);this._options.showMessage(message)},_setupPastePrompt:function(){var self=this;this._options.callbacks.onPasteReceived=function(){var message=self._options.paste.namePromptMessage,defaultVal=self._options.paste.defaultName;return self._options.showPrompt(message,defaultVal)}},_fileOrBlobRejected:function(id,name){this._totalFilesInBatch-=1;qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this,arguments)},_prepareItemsForUpload:function(items,params,endpoint){this._totalFilesInBatch=items.length;this._filesInBatchAddedToUi=0;qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this,arguments)}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,handlerImpl,api;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){},onUuidChanged:function(id,newUuid){}};qq.extend(options,o);log=options.log;function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){var cancelRetVal=handlerImpl.cancel(id);if(qq.isPromise(cancelRetVal)){cancelRetVal.then(function(){cancelSuccess(id)})}else if(cancelRetVal!==false){cancelSuccess(id)}},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},reset:function(){log("Resetting upload handler");api.cancelAll();queue=[];handlerImpl.reset()},expunge:function(id){return handlerImpl.expunge(id)},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}};return api};qq.UploadHandlerForm=function(o,uploadCompleteCallback,onUuidChange,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},formHandlerInstanceId=qq.getUniqueId(),api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var iframeName=iframe.id,fileId=getFileIdForIframeName(iframeName);onloadCallbacks[uuids[fileId]]=callback;detachLoadEvents[fileId]=qq(iframe).attach("load",function(){if(inputs[fileId]){log("Received iframe load event for CORS upload request (iframe name "+iframeName+")");postMessageCallbackTimers[iframeName]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for iframe name "+iframeName;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(iframeName,function(message){log("Received the following window message: '"+message+"'");var response=parseResponse(getFileIdForIframeName(iframeName),message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){log("Handling response for iframe name "+iframeName);clearTimeout(postMessageCallbackTimers[iframeName]);delete postMessageCallbackTimers[iframeName];detachLoadEvent(iframeName);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(iframeName);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(id,iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHtml=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHtml);if(innerHtml&&innerHtml.match(/^ ');iframe.setAttribute("id",iframeName);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}function expungeFile(id){delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(getIframeName(id));if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}}function getFileIdForIframeName(iframeName){return iframeName.split("_")[0]}function getIframeName(fileId){return fileId+"_"+formHandlerInstanceId}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){if(api.isValid(id)){return inputs[id].value.replace(/.*(\/|\\)/,"")}else{log(id+" is not a valid item ID.","error")}},isValid:function(id){return inputs[id]!==undefined},reset:function(){inputs=[];uuids=[];detachLoadEvents={};formHandlerInstanceId=qq.getUniqueId()},expunge:function(id){return expungeFile(id)},getUuid:function(id){return uuids[id]},cancel:function(id){var onCancelRetVal=options.onCancel(id,api.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeFile(id)})}else if(onCancelRetVal!==false){expungeFile(id);return true}return false},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,api.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(id,iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove()}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,onUuidChange,logCallback){"use strict";var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.supportedFeatures.chunking,resumeEnabled=options.resume.enabled&&chunkFiles&&qq.supportedFeatures.resume,resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);if(fileState[id]){delete fileState[id].xhr}uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(id,xhr){var response;try{response=qq.parseJson(xhr.responseText);if(response.newUuid!==undefined){log("Server requested UUID change from '"+fileState[id].uuid+"' to '"+response.newUuid+"'");fileState[id].uuid=response.newUuid;onUuidChanged(id,response.newUuid)}}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(id,xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex){var currentChunkIndex;for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}uploadNextChunk(id)}function onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex);calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}function handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex){var name=api.getName(id),firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part),onResumeRetVal;onResumeRetVal=options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume));if(qq.isPromise(onResumeRetVal)){log("Waiting for onResume promise to be fulfilled for "+id);onResumeRetVal.then(function(){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)},function(){log("onResume promise fulfilled - failure indicated. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)})}else if(onResumeRetVal!==false){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)}else{log("onResume callback returned false. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}function handleFileChunkingUpload(id,retry){var firstChunkIndex=0,persistedChunkInfoForResume;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex)}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{uploadNextChunk(id)}}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}function expungeItem(id){var xhr=fileState[id].xhr;if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]}api={add:function(fileOrBlobData){var id,persistedChunkData,uuid=qq.getUniqueId();if(qq.isFile(fileOrBlobData)){id=fileState.push({file:fileOrBlobData})-1}else if(qq.isBlob(fileOrBlobData.blob)){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}if(resumeEnabled){persistedChunkData=getPersistedChunkData(id);if(persistedChunkData){uuid=persistedChunkData.uuid}}fileState[id].uuid=uuid;return id},getName:function(id){if(api.isValid(id)){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}}else{log(id+" is not a valid item ID.","error")}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},expunge:function(id){return expungeItem(id)},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);if(this.isValid(id)){options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}}},cancel:function(id){var onCancelRetVal=options.onCancel(id,this.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeItem(id)})}else if(onCancelRetVal!==false){expungeItem(id);return true}return false},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};!function($){"use strict";var uploader,$el,init,dataStore,pluginOption,pluginOptions,addCallbacks,transformVariables,isValidCommand,delegateCommand;pluginOptions=["uploaderType"];init=function(options){if(options){var xformedOpts=transformVariables(options);addCallbacks(xformedOpts);if(pluginOption("uploaderType")==="basic"){uploader(new qq.FineUploaderBasic(xformedOpts))}else{uploader(new qq.FineUploader(xformedOpts))}}return $el};dataStore=function(key,val){var data=$el.data("fineuploader");if(val){if(data===undefined){data={}}data[key]=val;$el.data("fineuploader",data)}else{if(data===undefined){return null}return data[key]}};uploader=function(instanceToStore){return dataStore("uploader",instanceToStore)};pluginOption=function(option,optionVal){return dataStore(option,optionVal)};addCallbacks=function(transformedOpts){var callbacks=transformedOpts.callbacks={},uploaderInst=new qq.FineUploaderBasic;$.each(uploaderInst._options.callbacks,function(prop,func){var name,$callbackEl;name=/^on(\w+)/.exec(prop)[1];name=name.substring(0,1).toLowerCase()+name.substring(1);$callbackEl=$el;callbacks[prop]=function(){var args=Array.prototype.slice.call(arguments);return $callbackEl.triggerHandler(name,args)}})};transformVariables=function(source,dest){var xformed,arrayVals;if(dest===undefined){if(source.uploaderType!=="basic"){xformed={element:$el[0]}}else{xformed={}}}else{xformed=dest}$.each(source,function(prop,val){if($.inArray(prop,pluginOptions)>=0){pluginOption(prop,val)}else if(val instanceof $){xformed[prop]=val[0]}else if($.isPlainObject(val)){xformed[prop]={};transformVariables(val,xformed[prop])}else if($.isArray(val)){arrayVals=[];$.each(val,function(idx,arrayVal){if(arrayVal instanceof $){$.merge(arrayVals,arrayVal)}else{arrayVals.push(arrayVal)}});xformed[prop]=arrayVals}else{xformed[prop]=val}});if(dest===undefined){return xformed}};isValidCommand=function(command){return $.type(command)==="string"&&!command.match(/^_/)&&uploader()[command]!==undefined};delegateCommand=function(command){var xformedArgs=[],origArgs=Array.prototype.slice.call(arguments,1);transformVariables(origArgs,xformedArgs);return uploader()[command].apply(uploader(),xformedArgs)};$.fn.fineUploader=function(optionsOrCommand){var self=this,selfArgs=arguments,retVals=[];this.each(function(index,el){$el=$(el);if(uploader()&&isValidCommand(optionsOrCommand)){retVals.push(delegateCommand.apply(self,selfArgs));if(self.length===1){return false}}else if(typeof optionsOrCommand==="object"||!optionsOrCommand){init.apply(self,selfArgs)
+}else{$.error("Method "+optionsOrCommand+" does not exist on jQuery.fineUploader")}});if(retVals.length===1){return retVals[0]}else if(retVals.length>1){return retVals}return this}}(jQuery);!function($){"use strict";var rootDataKey="fineUploaderDnd",$el;function init(options){if(!options){options={}}options.dropZoneElements=[$el];var xformedOpts=transformVariables(options);addCallbacks(xformedOpts);dnd(new qq.DragAndDrop(xformedOpts));return $el}function dataStore(key,val){var data=$el.data(rootDataKey);if(val){if(data===undefined){data={}}data[key]=val;$el.data(rootDataKey,data)}else{if(data===undefined){return null}return data[key]}}function dnd(instanceToStore){return dataStore("dndInstance",instanceToStore)}function addCallbacks(transformedOpts){var callbacks=transformedOpts.callbacks={},dndInst=new qq.FineUploaderBasic;$.each(new qq.DragAndDrop.callbacks,function(prop,func){var name=prop,$callbackEl;$callbackEl=$el;callbacks[prop]=function(){var args=Array.prototype.slice.call(arguments),jqueryHandlerResult=$callbackEl.triggerHandler(name,args);return jqueryHandlerResult}})}function transformVariables(source,dest){var xformed,arrayVals;if(dest===undefined){xformed={}}else{xformed=dest}$.each(source,function(prop,val){if(val instanceof $){xformed[prop]=val[0]}else if($.isPlainObject(val)){xformed[prop]={};transformVariables(val,xformed[prop])}else if($.isArray(val)){arrayVals=[];$.each(val,function(idx,arrayVal){if(arrayVal instanceof $){$.merge(arrayVals,arrayVal)}else{arrayVals.push(arrayVal)}});xformed[prop]=arrayVals}else{xformed[prop]=val}});if(dest===undefined){return xformed}}function isValidCommand(command){return $.type(command)==="string"&&command==="dispose"&&dnd()[command]!==undefined}function delegateCommand(command){var xformedArgs=[],origArgs=Array.prototype.slice.call(arguments,1);transformVariables(origArgs,xformedArgs);return dnd()[command].apply(dnd(),xformedArgs)}$.fn.fineUploaderDnd=function(optionsOrCommand){var self=this,selfArgs=arguments,retVals=[];this.each(function(index,el){$el=$(el);if(dnd()&&isValidCommand(optionsOrCommand)){retVals.push(delegateCommand.apply(self,selfArgs));if(self.length===1){return false}}else if(typeof optionsOrCommand==="object"||!optionsOrCommand){init.apply(self,selfArgs)}else{$.error("Method "+optionsOrCommand+" does not exist in Fine Uploader's DnD module.")}});if(retVals.length===1){return retVals[0]}else if(retVals.length>1){return retVals}return this}}(jQuery);
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.2/fineuploader.css b/ajax/libs/file-uploader/3.6.2/fineuploader.css
new file mode 100644
index 000000000..11a436a06
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.2/fineuploader.css
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2013, Widen Enterprises info@fineuploader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+.qq-uploader {
+ position: relative;
+ width: 100%;
+}
+.qq-upload-button {
+ display: block;
+ width: 105px;
+ padding: 7px 0;
+ text-align: center;
+ background: #880000;
+ border-bottom: 1px solid #DDD;
+ color: #FFF;
+}
+.qq-upload-button-hover {
+ background: #CC0000;
+}
+.qq-upload-button-focus {
+ outline: 1px dotted #000000;
+}
+.qq-upload-drop-area, .qq-upload-extra-drop-area {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ min-height: 30px;
+ z-index: 2;
+ background: #FF9797;
+ text-align: center;
+}
+.qq-upload-drop-area span {
+ display: block;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ margin-top: -8px;
+ font-size: 16px;
+}
+.qq-upload-extra-drop-area {
+ position: relative;
+ margin-top: 50px;
+ font-size: 16px;
+ padding-top: 30px;
+ height: 20px;
+ min-height: 40px;
+}
+.qq-upload-drop-area-active {
+ background: #FF7171;
+}
+.qq-upload-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+.qq-upload-list li {
+ margin: 0;
+ padding: 9px;
+ line-height: 15px;
+ font-size: 16px;
+ background-color: #FFF0BD;
+}
+.qq-upload-file, .qq-upload-spinner, .qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text, .qq-upload-finished, .qq-upload-delete {
+ margin-right: 12px;
+}
+.qq-upload-file {
+}
+.qq-upload-spinner {
+ display: inline-block;
+ background: url("loading.gif");
+ width: 15px;
+ height: 15px;
+ vertical-align: text-bottom;
+}
+.qq-drop-processing {
+ display: none;
+}
+.qq-drop-processing-spinner {
+ display: inline-block;
+ background: url("processing.gif");
+ width: 24px;
+ height: 24px;
+ vertical-align: text-bottom;
+}
+.qq-upload-finished {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-retry, .qq-upload-delete {
+ display: none;
+ color: #000000;
+}
+.qq-upload-cancel, .qq-upload-delete {
+ color: #000000;
+}
+.qq-upload-retryable .qq-upload-retry {
+ display: inline;
+}
+.qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-delete {
+ font-size: 12px;
+ font-weight: normal;
+}
+.qq-upload-failed-text {
+ display: none;
+ font-style: italic;
+ font-weight: bold;
+}
+.qq-upload-failed-icon {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-fail .qq-upload-failed-text {
+ display: inline;
+}
+.qq-upload-retrying .qq-upload-failed-text {
+ display: inline;
+ color: #D60000;
+}
+.qq-upload-list li.qq-upload-success {
+ background-color: #5DA30C;
+ color: #FFFFFF;
+}
+.qq-upload-list li.qq-upload-fail {
+ background-color: #D60000;
+ color: #FFFFFF;
+}
+.qq-progress-bar {
+ background: -moz-linear-gradient(top, rgba(30,87,153,1) 0%, rgba(41,137,216,1) 50%, rgba(32,124,202,1) 51%, rgba(125,185,232,1) 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(30,87,153,1)), color-stop(50%,rgba(41,137,216,1)), color-stop(51%,rgba(32,124,202,1)), color-stop(100%,rgba(125,185,232,1))); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* IE10+ */
+ background: linear-gradient(to bottom, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* W3C */
+ width: 0%;
+ height: 15px;
+ border-radius: 6px;
+ margin-bottom: 3px;
+ display: none;
+}
diff --git a/ajax/libs/file-uploader/3.6.2/fineuploader.js b/ajax/libs/file-uploader/3.6.2/fineuploader.js
new file mode 100644
index 000000000..d3e4d086e
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.2/fineuploader.js
@@ -0,0 +1,4724 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: 3.6.2
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity != null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable && !variable.nodeType && Object.prototype.toString.call(variable) === '[object Object]';
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isArray = function(variable) {
+ "use strict";
+ return Object.prototype.toString.call(variable) === "[object Array]";
+}
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFile = function(maybeFile) {
+ "use strict";
+
+ return window.File && Object.prototype.toString.call(maybeFile) === '[object File]'
+};
+
+qq.isFileList = function(maybeFileList) {
+ return window.FileList && Object.prototype.toString.call(maybeFileList) === '[object FileList]'
+}
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+
+ return qq.isFile(maybeFileOrInput) || qq.isInput(maybeFileOrInput);
+};
+
+qq.isInput = function(maybeInput) {
+ if (window.HTMLInputElement) {
+ if (Object.prototype.toString.call(maybeInput) === '[object HTMLInputElement]') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ else if (maybeInput.tagName) {
+ if (maybeInput.tagName.toLowerCase() === 'input') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && Object.prototype.toString.call(maybeBlob) === '[object Blob]';
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice !== undefined || File.prototype.webkitSlice !== undefined || File.prototype.mozSlice !== undefined);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+
+ return first;
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+qq.ios = function() {
+ "use strict";
+ return navigator.userAgent.indexOf("iPad") !== -1
+ || navigator.userAgent.indexOf("iPod") !== -1
+ || navigator.userAgent.indexOf("iPhone") !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(objOrArray, callback) {
+ "use strict";
+ var keyOrIndex, retVal;
+ if (objOrArray) {
+ if (qq.isArray(objOrArray)) {
+ for (keyOrIndex = 0; keyOrIndex < objOrArray.length; keyOrIndex++) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ else {
+ for (keyOrIndex in objOrArray) {
+ if (Object.prototype.hasOwnProperty.call(objOrArray, keyOrIndex)) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+ }
+};
+
+//include any args that should be passed to the new function after the context arg
+qq.bind = function(oldFunc, context) {
+ if (qq.isFunction(oldFunc)) {
+ var args = Array.prototype.slice.call(arguments, 2);
+
+ return function() {
+ if (arguments.length) {
+ args = args.concat(Array.prototype.slice.call(arguments))
+ }
+ return oldFunc.apply(context, args);
+ };
+ }
+
+ throw new Error("first parameter must be a function!");
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ cookie;
+
+ qq.each(ca, function(idx, part) {
+ var cookiePart = part;
+ while (cookiePart.charAt(0)==' ') {
+ cookiePart = cookiePart.substring(1, cookiePart.length);
+ }
+
+ if (cookiePart.indexOf(nameEQ) === 0) {
+ cookie = cookiePart.substring(nameEQ.length, cookiePart.length);
+ return false;
+ }
+ });
+
+ return cookie;
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+qq.version="-unstable-";qq.supportedFeatures = (function() {
+ var supportsUploading,
+ supportsAjaxFileUploading,
+ supportsFolderDrop,
+ supportsChunking,
+ supportsResume,
+ supportsUploadViaPaste,
+ supportsUploadCors,
+ supportsDeleteFileCors;
+
+
+ function testSupportsFileInputElement() {
+ var supported = true,
+ tempInput;
+
+ try {
+ tempInput = document.createElement('input');
+ tempInput.type = 'file';
+ qq(tempInput).hide();
+
+ if(tempInput.disabled) {
+ supported = false;
+ }
+ }
+ catch(ex) {
+ supported = false;
+ }
+
+ return supported;
+ }
+
+ //only way to test for Filesystem API support since webkit does not expose the DataTransfer interface
+ function isChrome21OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[2][1-9]|Chrome\/[3-9][0-9]/) !== undefined;
+ }
+
+ //only way to test for complete Clipboard API support at this time
+ function isChrome14OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/) !== undefined;
+ }
+
+
+ supportsUploading = testSupportsFileInputElement();
+
+ supportsAjaxFileUploading = supportsUploading && qq.isXhrUploadSupported();
+
+ supportsFolderDrop = supportsAjaxFileUploading && isChrome21OrHigher();
+
+ supportsChunking = supportsAjaxFileUploading && qq.isFileChunkingSupported();
+
+ supportsResume = supportsAjaxFileUploading && supportsChunking && qq.areCookiesEnabled();
+
+ supportsUploadViaPaste = supportsAjaxFileUploading && isChrome14OrHigher();
+
+ supportsUploadCors = supportsUploading && (window.postMessage !== undefined || supportsAjaxFileUploading);
+
+ supportsDeleteFileCors = supportsAjaxFileUploading;
+
+
+ return {
+ uploading: supportsUploading,
+ ajaxUploading: supportsAjaxFileUploading,
+ fileDrop: supportsAjaxFileUploading, //NOTE: will also return true for touch-only devices. It's not currently possible to accurately test for touch-only devices
+ folderDrop: supportsFolderDrop,
+ chunking: supportsChunking,
+ resume: supportsResume,
+ uploadCustomHeaders: supportsAjaxFileUploading,
+ uploadNonMultipart: supportsAjaxFileUploading,
+ itemSizeValidation: supportsAjaxFileUploading,
+ uploadViaPaste: supportsUploadViaPaste,
+ progressBar: supportsAjaxFileUploading,
+ uploadCors: supportsUploadCors,
+ deleteFileCors: supportsDeleteFileCors,
+ canDetermineSize: supportsAjaxFileUploading
+ }
+
+}());
+/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallbacks = [],
+ failureCallbacks = [],
+ doneCallbacks = [],
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ if (onSuccess) {
+ successCallbacks.push(onSuccess);
+ }
+ if (onFailure) {
+ failureCallbacks.push(onFailure);
+ }
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+
+ return this;
+ },
+
+ done: function(callback) {
+ if (state === 0) {
+ doneCallbacks.push(callback);
+ }
+ else {
+ callback();
+ }
+
+ return this;
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallbacks.length) {
+ qq.each(successCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallbacks.length) {
+ qq.each(failureCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ }
+ };
+};
+
+qq.isPromise = function(maybePromise) {
+ return maybePromise && maybePromise.then && maybePromise.done;
+};/*globals qq*/
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ disposeSupport = new qq.DisposeSupport(),
+ options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input) {},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+
+ qq.extend(options, o);
+
+ // make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};qq.UploadData = function(uploaderProxy) {
+ var data = [],
+ byId = {},
+ byUuid = {},
+ byStatus = {},
+ api;
+
+ function getDataByIds(ids) {
+ if (qq.isArray(ids)) {
+ var entries = [];
+
+ qq.each(ids, function(idx, id) {
+ entries.push(data[byId[id]]);
+ });
+
+ return entries;
+ }
+
+ return data[byId[ids]];
+ }
+
+ function getDataByUuids(uuids) {
+ if (qq.isArray(uuids)) {
+ var entries = [];
+
+ qq.each(uuids, function(idx, uuid) {
+ entries.push(data[byUuid[uuid]]);
+ });
+
+ return entries;
+ }
+
+ return data[byUuid[uuids]];
+ }
+
+ function getDataByStatus(status) {
+ var statusResults = [],
+ statuses = [].concat(status);
+
+ qq.each(statuses, function(index, statusEnum) {
+ var statusResultIndexes = byStatus[statusEnum];
+
+ if (statusResultIndexes !== undefined) {
+ qq.each(statusResultIndexes, function(i, dataIndex) {
+ statusResults.push(data[dataIndex]);
+ });
+ }
+ });
+
+ return statusResults;
+ }
+
+ api = {
+ added: function(id) {
+ var uuid = uploaderProxy.getUuid(id),
+ name = uploaderProxy.getName(id),
+ size = uploaderProxy.getSize(id),
+ status = qq.status.SUBMITTING;
+
+ var index = data.push({
+ id: id,
+ name: name,
+ uuid: uuid,
+ size: size,
+ status: status
+ }) - 1;
+
+ byId[id] = index;
+
+ byUuid[uuid] = index;
+
+ if (byStatus[status] === undefined) {
+ byStatus[status] = [];
+ }
+ byStatus[status].push(index);
+
+ uploaderProxy.onStatusChange(id, undefined, status);
+ },
+
+ retrieve: function(optionalFilter) {
+ if (qq.isObject(optionalFilter) && data.length) {
+ if (optionalFilter.id !== undefined) {
+ return getDataByIds(optionalFilter.id);
+ }
+
+ else if (optionalFilter.uuid !== undefined) {
+ return getDataByUuids(optionalFilter.uuid);
+ }
+
+ else if (optionalFilter.status) {
+ return getDataByStatus(optionalFilter.status);
+ }
+ }
+ else {
+ return qq.extend([], data, true);
+ }
+ },
+
+ reset: function() {
+ data = [];
+ byId = {};
+ byUuid = {};
+ byStatus = {};
+ },
+
+ setStatus: function(id, newStatus) {
+ var dataIndex = byId[id],
+ oldStatus = data[dataIndex].status,
+ byStatusOldStatusIndex = qq.indexOf(byStatus[oldStatus], dataIndex);
+
+ byStatus[oldStatus].splice(byStatusOldStatusIndex, 1);
+
+ data[dataIndex].status = newStatus;
+
+ if (byStatus[newStatus] === undefined) {
+ byStatus[newStatus] = [];
+ }
+ byStatus[newStatus].push(dataIndex);
+
+ uploaderProxy.onStatusChange(id, oldStatus, newStatus);
+ },
+
+ uuidChanged: function(id, newUuid) {
+ var dataIndex = byId[id],
+ oldUuid = data[dataIndex].uuid;
+
+ data[dataIndex].uuid = newUuid;
+ byUuid[newUuid] = dataIndex;
+ delete byUuid[oldUuid];
+ }
+ };
+
+ return api;
+};
+
+qq.status = {
+ SUBMITTING: "submitting",
+ SUBMITTED: "submitted",
+ REJECTED: "rejected",
+ QUEUED: "queued",
+ CANCELED: "canceled",
+ UPLOADING: "uploading",
+ UPLOAD_RETRYING: "retrying upload",
+ UPLOAD_SUCCESSFUL: "upload successful",
+ UPLOAD_FAILED: "upload failed",
+ DELETE_FAILED: "delete failed",
+ DELETING: "deleting",
+ DELETED: "deleted"
+};qq.FineUploaderBasic = function(o) {
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true,
+ acceptFiles: null
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON, maybeXhr){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){},
+ onPasteReceived: function(blob) {},
+ onStatusChange: function(id, oldStatus, newStatus) {}
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ defaultResponseError: "Upload failure reason unknown",
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'misc_data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ },
+ camera: {
+ ios: false
+ }
+ };
+
+ qq.extend(this._options, o, true);
+
+ this._handleCameraAccess();
+
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData = this._createUploadDataTracker();
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader ' + qq.version + '] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader ' + qq.version + '] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function() {
+ return this._filesInProgress.length;
+ },
+ getNetUploads: function() {
+ return this._netUploaded;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netUploadedOrQueued++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData.reset();
+
+ if (this._pasteHandler) {
+ this._pasteHandler.reset();
+ }
+ },
+ addFiles: function(filesOrInputs, params, endpoint) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ fileOrInputIndex, fileOrInput, fileIndex;
+
+ if (filesOrInputs) {
+ if (!qq.isFileList(filesOrInputs)) {
+ filesOrInputs = [].concat(filesOrInputs);
+ }
+
+ for (fileOrInputIndex = 0; fileOrInputIndex < filesOrInputs.length; fileOrInputIndex+=1) {
+ fileOrInput = filesOrInputs[fileOrInputIndex];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ if (qq.isInput(fileOrInput) && qq.supportedFeatures.ajaxUploading) {
+ for (fileIndex = 0; fileIndex < fileOrInput.files.length; fileIndex++) {
+ verifiedFilesOrInputs.push(fileOrInput.files[fileIndex]);
+ }
+ }
+ else {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Received ' + verifiedFilesOrInputs.length + ' files or inputs.');
+ this._prepareItemsForUpload(verifiedFilesOrInputs, params, endpoint);
+ }
+ },
+ addBlobs: function(blobDataOrArray, params, endpoint) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._prepareItemsForUpload(verifiedBlobDataList, params, endpoint);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ doesExist: function(fileOrBlobId) {
+ return this._handler.isValid(fileOrBlobId);
+ },
+ getUploads: function(optionalFilter) {
+ return this._uploadData.retrieve(optionalFilter);
+ },
+ _handleCheckedCallback: function(details) {
+ var self = this,
+ callbackRetVal = details.callback();
+
+ if (qq.isPromise(callbackRetVal)) {
+ this.log(details.name + " - waiting for " + details.name + " promise to be fulfilled for " + details.identifier);
+ return callbackRetVal.then(
+ function(successParam) {
+ self.log(details.name + " promise success for " + details.identifier);
+ details.onSuccess(successParam);
+ },
+ function() {
+ if (details.onFailure) {
+ self.log(details.name + " promise failure for " + details.identifier);
+ details.onFailure();
+ }
+ else {
+ self.log(details.name + " promise failure for " + details.identifier);
+ }
+ });
+ }
+
+ if (callbackRetVal !== false) {
+ details.onSuccess(callbackRetVal);
+ }
+ else {
+ if (details.onFailure) {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Invoking failure callback.")
+ details.onFailure();
+ }
+ else {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Will not proceed.")
+ }
+ }
+
+ return callbackRetVal;
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.supportedFeatures.ajaxUploading,
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result, xhr);
+ },
+ onCancel: function(id, name) {
+ return self._handleCheckedCallback({
+ name: "onCancel",
+ callback: qq.bind(self._options.callbacks.onCancel, self, id, name),
+ onSuccess: qq.bind(self._onCancel, self, id, name),
+ identifier: id
+ });
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ onUuidChanged: function(id, newUuid) {
+ self._uploadData.uuidChanged(id, newUuid);
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ self._handleCheckedCallback({
+ name: "onPasteReceived",
+ callback: qq.bind(self._options.callbacks.onPasteReceived, self, blob),
+ onSuccess: qq.bind(self._handlePasteSuccess, self, blob),
+ identifier: "pasted image"
+ });
+ }
+ }
+ });
+ },
+ _createUploadDataTracker: function() {
+ var self = this;
+
+ return new qq.UploadData({
+ getName: function(id) {
+ return self.getName(id);
+ },
+ getUuid: function(id) {
+ return self.getUuid(id);
+ },
+ getSize: function(id) {
+ return self.getSize(id);
+ },
+ onStatusChange: function(id, oldStatus, newStatus) {
+ self._options.callbacks.onStatusChange(id, oldStatus, newStatus);
+ }
+ });
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total) {
+ //nothing to do yet in core uploader
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_FAILED);
+ this._netUploadedOrQueued--;
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_SUCCESSFUL);
+ this._netUploaded++;
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.CANCELED);
+
+ this._netUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected || qq.supportedFeatures.deleteFileCors));
+ },
+ _onSubmitDelete: function(id, onSuccessCallback) {
+ if (this._isDeletePossible()) {
+ return this._handleCheckedCallback({
+ name: "onSubmitDelete",
+ callback: qq.bind(this._options.callbacks.onSubmitDelete, this, id),
+ onSuccess: onSuccessCallback || qq.bind(this._deleteHandler.sendDelete, this, id, this.getUuid(id)),
+ identifier: id
+ });
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(id) {
+ this._uploadData.setStatus(id, qq.status.DELETING);
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this._uploadData.setStatus(id, qq.status.DELETE_FAILED);
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.DELETED);
+ this._netUploadedOrQueued--;
+ this._netUploaded--;
+ this._handler.expunge(id);
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.UPLOADING);
+ },
+ _onInputChange: function(input){
+ if (qq.supportedFeatures.ajaxUploading) {
+ this.addFiles(input.files);
+ }
+ else {
+ this.addFiles(input);
+ }
+
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems", "");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : this._options.text.defaultResponseError;
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ var validationDescriptors = this._getValidationDescriptors(items);
+
+ this._handleCheckedCallback({
+ name: "onValidateBatch",
+ callback: qq.bind(this._options.callbacks.onValidateBatch, this, validationDescriptors),
+ onSuccess: qq.bind(this._onValidateBatchCallbackSuccess, this, validationDescriptors, items, params, endpoint),
+ identifier: "batch validation"
+ });
+ },
+ _upload: function(blobOrFileContainer, params, endpoint) {
+ var id = this._handler.add(blobOrFileContainer),
+ name = this._handler.getName(id);
+
+ this._uploadData.added(id);
+
+ if (params) {
+ this.setParams(params, id);
+ }
+
+ if (endpoint) {
+ this.setEndpoint(endpoint, id);
+ }
+
+ this._handleCheckedCallback({
+ name: "onSubmit",
+ callback: qq.bind(this._options.callbacks.onSubmit, this, id, name),
+ onSuccess: qq.bind(this._onSubmitCallbackSuccess, this, id, name),
+ onFailure: qq.bind(this._fileOrBlobRejected, this, id, name),
+ identifier: id
+ });
+ },
+ _onSubmitCallbackSuccess: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.SUBMITTED);
+
+ this._onSubmit(id, name);
+ this._options.callbacks.onSubmitted(id, name);
+
+ if (this._options.autoUpload) {
+ if (!this._handler.upload(id)) {
+ this._uploadData.setStatus(id, qq.status.QUEUED);
+ }
+ }
+ else {
+ this._storeForLater(id);
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _onValidateBatchCallbackSuccess: function(validationDescriptors, items, params, endpoint) {
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netUploadedOrQueued + validationDescriptors.length;
+
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ if (items.length > 0) {
+ this._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(this._options.callbacks.onValidate, this, items[0]),
+ onSuccess: qq.bind(this._onValidateCallbackSuccess, this, items, 0, params, endpoint),
+ onFailure: qq.bind(this._onValidateCallbackFailure, this, items, 0, params, endpoint),
+ identifier: "Item '" + items[0].name + "', size: " + items[0].size
+ });
+ }
+ else {
+ this._itemError("noFilesError", "");
+ }
+ }
+ else {
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ },
+ _onValidateCallbackSuccess: function(items, index, params, endpoint) {
+ var nextIndex = index+1,
+ validationDescriptor = this._getValidationDescriptor(items[index]),
+ validItem = false;
+
+ if (this._validateFileOrBlobData(items[index], validationDescriptor)) {
+ validItem = true;
+ this._upload(items[index], params, endpoint);
+ }
+
+ this._maybeProcessNextItemAfterOnValidateCallback(validItem, items, nextIndex, params, endpoint);
+ },
+ _onValidateCallbackFailure: function(items, index, params, endpoint) {
+ var nextIndex = index+ 1;
+
+ this._fileOrBlobRejected(undefined, items[0].name);
+
+ this._maybeProcessNextItemAfterOnValidateCallback(false, items, nextIndex, params, endpoint);
+ },
+ _maybeProcessNextItemAfterOnValidateCallback: function(validItem, items, index, params, endpoint) {
+ var self = this;
+
+ if (items.length > index) {
+ if (validItem || !this._options.validation.stopOnFirstInvalidFile) {
+ //use setTimeout to prevent a stack overflow with a large number of files in the batch & non-promissory callbacks
+ setTimeout(function() {
+ var validationDescriptor = self._getValidationDescriptor(items[index]);
+
+ self._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(self._options.callbacks.onValidate, self, items[index]),
+ onSuccess: qq.bind(self._onValidateCallbackSuccess, self, items, index, params, endpoint),
+ onFailure: qq.bind(self._onValidateCallbackFailure, self, items, index, params, endpoint),
+ identifier: "Item '" + validationDescriptor.name + "', size: " + validationDescriptor.size
+ });
+ }, 0);
+ }
+ }
+ },
+ _validateFileOrBlobData: function(item, validationDescriptor) {
+ var name = validationDescriptor.name,
+ size = validationDescriptor.size,
+ valid = true;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ valid = false;
+ }
+
+ if (qq.isFileOrInput(item) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ valid = false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ valid = false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ valid = false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ valid = false;
+ }
+
+ if (!valid) {
+ this._fileOrBlobRejected(undefined, name);
+ }
+
+ return valid;
+ },
+ _fileOrBlobRejected: function(id, name) {
+ if (id !== undefined) {
+ this._uploadData.setStatus(id, qq.status.REJECTED);
+ }
+ },
+ _itemError: function(code, nameOrNames) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ names = [].concat(nameOrNames),
+ name = names[0],
+ extensionsForMessage, placeholderMatch;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ placeholderMatch = message.match(/(\{\w+\})/g);
+ if (placeholderMatch !== null) {
+ qq.each(placeholderMatch, function(idx, placeholder) {
+ r(placeholder, names[idx]);
+ });
+ }
+
+ this._options.callbacks.onError(null, name, message, undefined);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message, undefined);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1000;
+ i++;
+ } while (bytes > 999);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size !== undefined) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ },
+ _handleCameraAccess: function() {
+ if (this._options.camera.ios && qq.ios()) {
+ this._options.multiple = false;
+
+ if (this._options.validation.acceptFiles === null) {
+ this._options.validation.acceptFiles = "image/*;capture=camera";
+ }
+ else {
+ this._options.validation.acceptFiles += ",image/*;capture=camera";
+ }
+ }
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz,
+ droppedFiles = [],
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropZoneElements: [],
+ hideDropZonesBeforeEnter: false,
+ allowMultipleItems: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: new qq.DragAndDrop.callbacks()
+ };
+
+ qq.extend(options, o, true);
+
+ setupDragDrop();
+
+ function uploadDroppedFiles(files) {
+ options.callbacks.dropLog('Grabbed ' + files.length + " dropped files.");
+ dz.dropDisabled(false);
+ options.callbacks.processingDroppedFilesComplete(files);
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i,
+ parseEntryPromise = new qq.Promise();
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ droppedFiles.push(file);
+ parseEntryPromise.success();
+ },
+ function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+ else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ var entriesLeft = entries.length;
+
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]).done(function() {
+ entriesLeft-=1;
+
+ if (entriesLeft === 0) {
+ parseEntryPromise.success();
+ }
+ });
+ }
+
+ if (!entries.length) {
+ parseEntryPromise.success();
+ }
+ }, function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+
+ return parseEntryPromise;
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry,
+ pendingFolderPromises = [],
+ handleDataTransferPromise = new qq.Promise();
+
+ options.callbacks.processingDroppedFiles();
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.allowMultipleItems) {
+ options.callbacks.processingDroppedFilesComplete([]);
+ options.callbacks.dropError('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ handleDataTransferPromise.failure();
+ }
+ else {
+ droppedFiles = [];
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ }
+
+ else {
+ pendingFolderPromises.push(traverseFileTree(entry).done(function() {
+ pendingFolderPromises.pop();
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }));
+ }
+ }
+ }
+ }
+ else {
+ droppedFiles = dataTransfer.files;
+ }
+
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }
+
+ return handleDataTransferPromise;
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer).done(function() {
+ uploadDroppedFiles(droppedFiles);
+ });
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ var dropZones = options.dropZoneElements;
+
+ qq.each(dropZones, function(idx, dropZone) {
+ setupDropzone(dropZone);
+ })
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (dropZones.length && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).css({display: 'block'});
+ });
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropZonesBeforeEnter && qq.FineUploader.prototype._leaving_document_out(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setupExtraDropzone: function(element) {
+ options.dropZoneElements.push(element);
+ setupDropzone(element);
+ },
+
+ removeDropzone: function(element) {
+ var i,
+ dzs = options.dropZoneElements;
+
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+qq.DragAndDrop.callbacks = function() {
+ return {
+ processingDroppedFiles: function() {},
+ processingDroppedFilesComplete: function(files) {},
+ dropError: function(code, errorSpecifics) {
+ qq.log("Drag & drop error code '" + code + " with these specifics: '" + errorSpecifics + "'", "error");
+ },
+ dropLog: function(message, level) {
+ qq.log(message, level);
+ }
+ }
+}
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file",
+ unsupportedBrowser: "Unrecoverable error - this browser does not permit file uploading of any kind."
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false,
+ prependFiles: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+
+ if (!qq.supportedFeatures.uploading || (this._options.cors.expected && !qq.supportedFeatures.uploadCors)) {
+ this._options.element.innerHTML = "" + this._options.messages.unsupportedBrowser + "
"
+ }
+ else {
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dropZoneElements = this._options.dragAndDrop.extraDropzones,
+ preventSelectFiles;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ dropZoneElements.push(this._find(this._options.element, 'drop'));
+ }
+
+ return new qq.DragAndDrop({
+ dropZoneElements: dropZoneElements,
+ hideDropZonesBeforeEnter: this._options.dragAndDrop.hideDropzones,
+ allowMultipleItems: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ processingDroppedFiles: function() {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ },
+ processingDroppedFilesComplete: function(files) {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ dropError: function(code, errorData) {
+ self._itemError(code, errorData);
+ },
+ dropLog: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type) {
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name) {
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ var onSuccessCallback = qq.bind(this._onSubmitDeleteSuccess, this, id);
+
+ qq.FineUploaderBasic.prototype._onSubmitDelete.call(this, id, onSuccessCallback);
+ },
+ _onSubmitDeleteSuccess: function(id) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ if (this._options.display.prependFiles) {
+ this._prependItem(item);
+ }
+ else {
+ this._listElement.appendChild(item);
+ }
+ this._filesInBatchAddedToUi += 1;
+
+ if (this._options.display.fileSizeOnSubmit && qq.supportedFeatures.ajaxUploading) {
+ this._displayFileSize(id);
+ }
+ },
+ _prependItem: function(item) {
+ var parentEl = this._listElement,
+ beforeEl = parentEl.firstChild;
+
+ if (this._totalFilesInBatch > 1 && this._filesInBatchAddedToUi > 0) {
+ beforeEl = qq(parentEl).children()[this._filesInBatchAddedToUi - 1].nextSibling;
+
+ }
+
+ parentEl.insertBefore(item, beforeEl);
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = item.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ },
+ _fileOrBlobRejected: function(id, name) {
+ this._totalFilesInBatch -= 1;
+ qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this, arguments);
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ this._totalFilesInBatch = items.length;
+ this._filesInBatchAddedToUi = 0;
+ qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this, arguments);
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, handlerImpl, api;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){},
+ onUuidChanged: function(id, newUuid){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.supportedFeatures.ajaxUploading) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, options.onUuidChanged, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, options.onUuidChanged, log);
+ }
+
+ function cancelSuccess(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ dequeue(id);
+ }
+
+
+ api = {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ handlerImpl.upload(id);
+ return true;
+ }
+
+ return false;
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ var cancelRetVal = handlerImpl.cancel(id);
+
+ if (qq.isPromise(cancelRetVal)) {
+ cancelRetVal.then(function() {
+ cancelSuccess(id);
+ });
+ }
+ else if (cancelRetVal !== false) {
+ cancelSuccess(id);
+ }
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ api.cancelAll();
+ queue = [];
+ handlerImpl.reset();
+ },
+ expunge: function(id) {
+ return handlerImpl.expunge(id);
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, onUuidChange, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ formHandlerInstanceId = qq.getUniqueId(),
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var iframeName = iframe.id,
+ fileId = getFileIdForIframeName(iframeName);
+
+ onloadCallbacks[uuids[fileId]] = callback;
+
+ detachLoadEvents[fileId] = qq(iframe).attach('load', function() {
+ if (inputs[fileId]) {
+ log("Received iframe load event for CORS upload request (iframe name " + iframeName + ")");
+
+ postMessageCallbackTimers[iframeName] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for iframe name " + iframeName;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(iframeName, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = parseResponse(getFileIdForIframeName(iframeName), message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ log("Handling response for iframe name " + iframeName);
+ clearTimeout(postMessageCallbackTimers[iframeName]);
+ delete postMessageCallbackTimers[iframeName];
+
+ detachLoadEvent(iframeName);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(iframeName);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(id, iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHtml = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHtml);
+ //plain text response may be wrapped in tag
+ if (innerHtml && innerHtml.match(/^ ');
+
+ iframe.setAttribute('id', iframeName);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+ function expungeFile(id) {
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(getIframeName(id));
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ }
+
+ function getFileIdForIframeName(iframeName) {
+ return iframeName.split("_")[0];
+ }
+
+ function getIframeName(fileId) {
+ return fileId + "_" + formHandlerInstanceId;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ formHandlerInstanceId = qq.getUniqueId();
+ },
+ expunge: function(id) {
+ return expungeFile(id);
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, api.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeFile(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeFile(id);
+ return true;
+ }
+
+ return false;
+ },
+
+ upload: function(id) {
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, api.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(id, iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, onUuidChange, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.supportedFeatures.chunking,
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.supportedFeatures.resume,
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+ options.onComplete(id, name, response, xhr);
+
+ if (fileState[id]) {
+ delete fileState[id].xhr;
+ }
+
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(id, xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+
+ if (response.newUuid !== undefined) {
+ log("Server requested UUID change from '" + fileState[id].uuid + "' to '" + response.newUuid + "'");
+ fileState[id].uuid = response.newUuid;
+ onUuidChanged(id, response.newUuid);
+ }
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(id, xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex) {
+ var currentChunkIndex;
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+
+ function handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex) {
+ var name = api.getName(id),
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part),
+ onResumeRetVal;
+
+ onResumeRetVal = options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume));
+ if (qq.isPromise(onResumeRetVal)) {
+ log("Waiting for onResume promise to be fulfilled for " + id);
+ onResumeRetVal.then(
+ function() {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ },
+ function() {
+ log("onResume promise fulfilled - failure indicated. Will not resume.")
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ );
+ }
+ else if (onResumeRetVal !== false) {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ }
+ else {
+ log("onResume callback returned false. Will not resume.");
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var firstChunkIndex = 0,
+ persistedChunkInfoForResume;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex);
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ uploadNextChunk(id);
+ }
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+ function expungeItem(id) {
+ var xhr = fileState[id].xhr;
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ }
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id, persistedChunkData,
+ uuid = qq.getUniqueId();
+
+ if (qq.isFile(fileOrBlobData)) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (qq.isBlob(fileOrBlobData.blob)) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ if (resumeEnabled) {
+ persistedChunkData = getPersistedChunkData(id);
+
+ if (persistedChunkData) {
+ uuid = persistedChunkData.uuid;
+ }
+ }
+
+ fileState[id].uuid = uuid;
+
+ return id;
+ },
+ getName: function(id){
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ expunge: function(id) {
+ return expungeItem(id);
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry) {
+ var name = this.getName(id);
+
+ if (this.isValid(id)) {
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ }
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, this.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeItem(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeItem(id);
+ return true;
+ }
+
+ return false;
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
diff --git a/ajax/libs/file-uploader/3.6.2/fineuploader.min.css b/ajax/libs/file-uploader/3.6.2/fineuploader.min.css
new file mode 100644
index 000000000..110e8fa52
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.2/fineuploader.min.css
@@ -0,0 +1 @@
+.qq-uploader{position:relative;width:100%;}.qq-upload-button{display:block;width:105px;padding:7px 0;text-align:center;background:#800;border-bottom:1px solid #DDD;color:#FFF;}.qq-upload-button-hover{background:#C00;}.qq-upload-button-focus{outline:1px dotted #000;}.qq-upload-drop-area,.qq-upload-extra-drop-area{position:absolute;top:0;left:0;width:100%;height:100%;min-height:30px;z-index:2;background:#FF9797;text-align:center;}.qq-upload-drop-area span{display:block;position:absolute;top:50%;width:100%;margin-top:-8px;font-size:16px;}.qq-upload-extra-drop-area{position:relative;margin-top:50px;font-size:16px;padding-top:30px;height:20px;min-height:40px;}.qq-upload-drop-area-active{background:#FF7171;}.qq-upload-list{margin:0;padding:0;list-style:none;}.qq-upload-list li{margin:0;padding:9px;line-height:15px;font-size:16px;background-color:#FFF0BD;}.qq-upload-file,.qq-upload-spinner,.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-failed-text,.qq-upload-finished,.qq-upload-delete{margin-right:12px;}.qq-upload-spinner{display:inline-block;background:url("loading.gif");width:15px;height:15px;vertical-align:text-bottom;}.qq-drop-processing{display:none;}.qq-drop-processing-spinner{display:inline-block;background:url("processing.gif");width:24px;height:24px;vertical-align:text-bottom;}.qq-upload-finished{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-retry,.qq-upload-delete{display:none;color:#000;}.qq-upload-cancel,.qq-upload-delete{color:#000;}.qq-upload-retryable .qq-upload-retry{display:inline;}.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-delete{font-size:12px;font-weight:normal;}.qq-upload-failed-text{display:none;font-style:italic;font-weight:bold;}.qq-upload-failed-icon{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-fail .qq-upload-failed-text{display:inline;}.qq-upload-retrying .qq-upload-failed-text{display:inline;color:#D60000;}.qq-upload-list li.qq-upload-success{background-color:#5DA30C;color:#FFF;}.qq-upload-list li.qq-upload-fail{background-color:#D60000;color:#FFF;}.qq-progress-bar{background:-moz-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,rgba(30,87,153,1)),color-stop(50%,rgba(41,137,216,1)),color-stop(51%,rgba(32,124,202,1)),color-stop(100%,rgba(125,185,232,1)));background:-webkit-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-o-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-ms-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:linear-gradient(to bottom,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);width:0;height:15px;border-radius:6px;margin-bottom:3px;display:none;}
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.2/fineuploader.min.js b/ajax/libs/file-uploader/3.6.2/fineuploader.min.js
new file mode 100644
index 000000000..9b585ef38
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.2/fineuploader.min.js
@@ -0,0 +1,15 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: 3.6.2
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!=null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable&&!variable.nodeType&&Object.prototype.toString.call(variable)==="[object Object]"};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isArray=function(variable){"use strict";return Object.prototype.toString.call(variable)==="[object Array]"};qq.isString=function(maybeString){"use strict";return Object.prototype.toString.call(maybeString)==="[object String]"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFile=function(maybeFile){"use strict";return window.File&&Object.prototype.toString.call(maybeFile)==="[object File]"};qq.isFileList=function(maybeFileList){return window.FileList&&Object.prototype.toString.call(maybeFileList)==="[object FileList]"};qq.isFileOrInput=function(maybeFileOrInput){"use strict";return qq.isFile(maybeFileOrInput)||qq.isInput(maybeFileOrInput)};qq.isInput=function(maybeInput){if(window.HTMLInputElement){if(Object.prototype.toString.call(maybeInput)==="[object HTMLInputElement]"){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}else if(maybeInput.tagName){if(maybeInput.tagName.toLowerCase()==="input"){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&Object.prototype.toString.call(maybeBlob)==="[object Blob]"};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice!==undefined||File.prototype.webkitSlice!==undefined||File.prototype.mozSlice!==undefined)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}});return first};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{defaultResponseError:"Upload failure reason unknown",sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"misc_data",paramNames:{name:"qqblobname"}},paste:{targetElement:null,defaultName:"pasted_image"},camera:{ios:false}};qq.extend(this._options,o,true);this._handleCameraAccess();this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData=this._createUploadDataTracker();this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}if(this._options.paste.targetElement){this._pasteHandler=this._createPasteHandler()}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader "+qq.version+"] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader "+qq.version+"] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},getNetUploads:function(){return this._netUploaded},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._netUploadedOrQueued++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset();this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData.reset();if(this._pasteHandler){this._pasteHandler.reset()}},addFiles:function(filesOrInputs,params,endpoint){var self=this,verifiedFilesOrInputs=[],fileOrInputIndex,fileOrInput,fileIndex;if(filesOrInputs){if(!qq.isFileList(filesOrInputs)){filesOrInputs=[].concat(filesOrInputs)}for(fileOrInputIndex=0;fileOrInputIndex=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||qq.supportedFeatures.deleteFileCors)},_onSubmitDelete:function(id,onSuccessCallback){if(this._isDeletePossible()){return this._handleCheckedCallback({name:"onSubmitDelete",callback:qq.bind(this._options.callbacks.onSubmitDelete,this,id),onSuccess:onSuccessCallback||qq.bind(this._deleteHandler.sendDelete,this,id,this.getUuid(id)),identifier:id})}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(id){this._uploadData.setStatus(id,qq.status.DELETING)},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);
+if(isError){this._uploadData.setStatus(id,qq.status.DELETE_FAILED);this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status,xhr)}else{this._uploadData.setStatus(id,qq.status.DELETED);this._netUploadedOrQueued--;this._netUploaded--;this._handler.expunge(id);this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){this._uploadData.setStatus(id,qq.status.UPLOADING)},_onInputChange:function(input){if(qq.supportedFeatures.ajaxUploading){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0&&this._netUploadedOrQueued+1>itemLimit){this._itemError("retryFailTooManyItems","");return false}this.log("Retrying upload for '"+fileName+"' (id: "+id+")...");this._filesInProgress.push(id);return true}else{this.log("'"+id+"' is not a valid file ID","error");return false}},_maybeParseAndSendUploadError:function(id,name,response,xhr){if(!response.success){if(xhr&&xhr.status!==200&&!response.error){this._options.callbacks.onError(id,name,"XHR returned response code "+xhr.status,xhr)}else{var errorReason=response.error?response.error:this._options.text.defaultResponseError;this._options.callbacks.onError(id,name,errorReason,xhr)}}},_prepareItemsForUpload:function(items,params,endpoint){var validationDescriptors=this._getValidationDescriptors(items);this._handleCheckedCallback({name:"onValidateBatch",callback:qq.bind(this._options.callbacks.onValidateBatch,this,validationDescriptors),onSuccess:qq.bind(this._onValidateBatchCallbackSuccess,this,validationDescriptors,items,params,endpoint),identifier:"batch validation"})},_upload:function(blobOrFileContainer,params,endpoint){var id=this._handler.add(blobOrFileContainer),name=this._handler.getName(id);this._uploadData.added(id);if(params){this.setParams(params,id)}if(endpoint){this.setEndpoint(endpoint,id)}this._handleCheckedCallback({name:"onSubmit",callback:qq.bind(this._options.callbacks.onSubmit,this,id,name),onSuccess:qq.bind(this._onSubmitCallbackSuccess,this,id,name),onFailure:qq.bind(this._fileOrBlobRejected,this,id,name),identifier:id})},_onSubmitCallbackSuccess:function(id,name){this._uploadData.setStatus(id,qq.status.SUBMITTED);this._onSubmit(id,name);this._options.callbacks.onSubmitted(id,name);if(this._options.autoUpload){if(!this._handler.upload(id)){this._uploadData.setStatus(id,qq.status.QUEUED)}}else{this._storeForLater(id)}},_storeForLater:function(id){this._storedIds.push(id)},_onValidateBatchCallbackSuccess:function(validationDescriptors,items,params,endpoint){var errorMessage,itemLimit=this._options.validation.itemLimit,proposedNetFilesUploadedOrQueued=this._netUploadedOrQueued+validationDescriptors.length;if(itemLimit===0||proposedNetFilesUploadedOrQueued<=itemLimit){if(items.length>0){this._handleCheckedCallback({name:"onValidate",callback:qq.bind(this._options.callbacks.onValidate,this,items[0]),onSuccess:qq.bind(this._onValidateCallbackSuccess,this,items,0,params,endpoint),onFailure:qq.bind(this._onValidateCallbackFailure,this,items,0,params,endpoint),identifier:"Item '"+items[0].name+"', size: "+items[0].size})}else{this._itemError("noFilesError","")}}else{errorMessage=this._options.messages.tooManyItemsError.replace(/\{netItems\}/g,proposedNetFilesUploadedOrQueued).replace(/\{itemLimit\}/g,itemLimit);this._batchError(errorMessage)}},_onValidateCallbackSuccess:function(items,index,params,endpoint){var nextIndex=index+1,validationDescriptor=this._getValidationDescriptor(items[index]),validItem=false;if(this._validateFileOrBlobData(items[index],validationDescriptor)){validItem=true;this._upload(items[index],params,endpoint)}this._maybeProcessNextItemAfterOnValidateCallback(validItem,items,nextIndex,params,endpoint)},_onValidateCallbackFailure:function(items,index,params,endpoint){var nextIndex=index+1;this._fileOrBlobRejected(undefined,items[0].name);this._maybeProcessNextItemAfterOnValidateCallback(false,items,nextIndex,params,endpoint)},_maybeProcessNextItemAfterOnValidateCallback:function(validItem,items,index,params,endpoint){var self=this;if(items.length>index){if(validItem||!this._options.validation.stopOnFirstInvalidFile){setTimeout(function(){var validationDescriptor=self._getValidationDescriptor(items[index]);self._handleCheckedCallback({name:"onValidate",callback:qq.bind(self._options.callbacks.onValidate,self,items[index]),onSuccess:qq.bind(self._onValidateCallbackSuccess,self,items,index,params,endpoint),onFailure:qq.bind(self._onValidateCallbackFailure,self,items,index,params,endpoint),identifier:"Item '"+validationDescriptor.name+"', size: "+validationDescriptor.size})},0)}}},_validateFileOrBlobData:function(item,validationDescriptor){var name=validationDescriptor.name,size=validationDescriptor.size,valid=true;if(this._options.callbacks.onValidate(validationDescriptor)===false){valid=false}if(qq.isFileOrInput(item)&&!this._isAllowedExtension(name)){this._itemError("typeError",name);valid=false}else if(size===0){this._itemError("emptyError",name);valid=false}else if(size&&this._options.validation.sizeLimit&&size>this._options.validation.sizeLimit){this._itemError("sizeError",name);valid=false}else if(size&&size999);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size!==undefined){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}},_handleCameraAccess:function(){if(this._options.camera.ios&&qq.ios()){this._options.multiple=false;if(this._options.validation.acceptFiles===null){this._options.validation.acceptFiles="image/*;capture=camera"}else{this._options.validation.acceptFiles+=",image/*;capture=camera"}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,droppedFiles=[],disposeSupport=new qq.DisposeSupport;options={dropZoneElements:[],hideDropZonesBeforeEnter:false,allowMultipleItems:true,classes:{dropActive:null},callbacks:new qq.DragAndDrop.callbacks};qq.extend(options,o,true);setupDragDrop();function uploadDroppedFiles(files){options.callbacks.dropLog("Grabbed "+files.length+" dropped files.");dz.dropDisabled(false);options.callbacks.processingDroppedFilesComplete(files)}function traverseFileTree(entry){var dirReader,i,parseEntryPromise=new qq.Promise;if(entry.isFile){entry.file(function(file){droppedFiles.push(file);parseEntryPromise.success()},function(fileError){options.callbacks.dropLog("Problem parsing '"+entry.fullPath+"'. FileError code "+fileError.code+".","error");parseEntryPromise.failure()})}else if(entry.isDirectory){dirReader=entry.createReader();dirReader.readEntries(function(entries){var entriesLeft=entries.length;for(i=0;i1&&!options.allowMultipleItems){options.callbacks.processingDroppedFilesComplete([]);options.callbacks.dropError("tooManyFilesError","");dz.dropDisabled(false);handleDataTransferPromise.failure()}else{droppedFiles=[];if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file",unsupportedBrowser:"Unrecoverable error - this browser does not permit file uploading of any kind."},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false,prependFiles:false},paste:{promptForName:false,namePromptMessage:"Please name this image"},showMessage:function(message){setTimeout(function(){window.alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=window.confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)},showPrompt:function(message,defaultValue){var promise=new qq.Promise,retVal=window.prompt(message,defaultValue);if(retVal!=null&&qq.trimStr(retVal).length>0){promise.success(retVal)}else{promise.failure("Undefined or invalid user-supplied value.")}return promise}},true);qq.extend(this._options,o,true);if(!qq.supportedFeatures.uploading||this._options.cors.expected&&!qq.supportedFeatures.uploadCors){this._options.element.innerHTML=""+this._options.messages.unsupportedBrowser+"
"}else{this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop();if(this._options.paste.targetElement&&this._options.paste.promptForName){this._setupPastePrompt()}this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0}};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop();this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dropZoneElements=this._options.dragAndDrop.extraDropzones,preventSelectFiles;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){dropZoneElements.push(this._find(this._options.element,"drop"))}return new qq.DragAndDrop({dropZoneElements:dropZoneElements,hideDropZonesBeforeEnter:this._options.dragAndDrop.hideDropzones,allowMultipleItems:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{processingDroppedFiles:function(){var input=self._button.getInput();qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)},processingDroppedFilesComplete:function(files){var input=self._button.getInput();qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles);if(files){self.addFiles(files)}},dropError:function(code,errorData){self._itemError(code,errorData)},dropLog:function(message,level){self.log(message,level)}}})},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){var item=this.getItemByFileId(id);if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}else{qq(item).addClass(this._classes.retryable);return false}},_onSubmitDelete:function(id){var onSuccessCallback=qq.bind(this._onSubmitDeleteSuccess,this,id);qq.FineUploaderBasic.prototype._onSubmitDelete.call(this,id,onSuccessCallback)},_onSubmitDeleteSuccess:function(id){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}if(this._options.display.prependFiles){this._prependItem(item)}else{this._listElement.appendChild(item)}this._filesInBatchAddedToUi+=1;if(this._options.display.fileSizeOnSubmit&&qq.supportedFeatures.ajaxUploading){this._displayFileSize(id)}},_prependItem:function(item){var parentEl=this._listElement,beforeEl=parentEl.firstChild;if(this._totalFilesInBatch>1&&this._filesInBatchAddedToUi>0){beforeEl=qq(parentEl).children()[this._filesInBatchAddedToUi-1].nextSibling}parentEl.insertBefore(item,beforeEl)},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=item.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)
+}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_itemError:function(code,name){var message=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(message)},_batchError:function(message){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments);this._options.showMessage(message)},_setupPastePrompt:function(){var self=this;this._options.callbacks.onPasteReceived=function(){var message=self._options.paste.namePromptMessage,defaultVal=self._options.paste.defaultName;return self._options.showPrompt(message,defaultVal)}},_fileOrBlobRejected:function(id,name){this._totalFilesInBatch-=1;qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this,arguments)},_prepareItemsForUpload:function(items,params,endpoint){this._totalFilesInBatch=items.length;this._filesInBatchAddedToUi=0;qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this,arguments)}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,handlerImpl,api;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){},onUuidChanged:function(id,newUuid){}};qq.extend(options,o);log=options.log;function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){var cancelRetVal=handlerImpl.cancel(id);if(qq.isPromise(cancelRetVal)){cancelRetVal.then(function(){cancelSuccess(id)})}else if(cancelRetVal!==false){cancelSuccess(id)}},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},reset:function(){log("Resetting upload handler");api.cancelAll();queue=[];handlerImpl.reset()},expunge:function(id){return handlerImpl.expunge(id)},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}};return api};qq.UploadHandlerForm=function(o,uploadCompleteCallback,onUuidChange,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},formHandlerInstanceId=qq.getUniqueId(),api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var iframeName=iframe.id,fileId=getFileIdForIframeName(iframeName);onloadCallbacks[uuids[fileId]]=callback;detachLoadEvents[fileId]=qq(iframe).attach("load",function(){if(inputs[fileId]){log("Received iframe load event for CORS upload request (iframe name "+iframeName+")");postMessageCallbackTimers[iframeName]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for iframe name "+iframeName;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(iframeName,function(message){log("Received the following window message: '"+message+"'");var response=parseResponse(getFileIdForIframeName(iframeName),message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){log("Handling response for iframe name "+iframeName);clearTimeout(postMessageCallbackTimers[iframeName]);delete postMessageCallbackTimers[iframeName];detachLoadEvent(iframeName);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(iframeName);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(id,iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHtml=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHtml);if(innerHtml&&innerHtml.match(/^ ');iframe.setAttribute("id",iframeName);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}function expungeFile(id){delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(getIframeName(id));if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}}function getFileIdForIframeName(iframeName){return iframeName.split("_")[0]}function getIframeName(fileId){return fileId+"_"+formHandlerInstanceId}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){if(api.isValid(id)){return inputs[id].value.replace(/.*(\/|\\)/,"")}else{log(id+" is not a valid item ID.","error")}},isValid:function(id){return inputs[id]!==undefined},reset:function(){inputs=[];uuids=[];detachLoadEvents={};formHandlerInstanceId=qq.getUniqueId()},expunge:function(id){return expungeFile(id)},getUuid:function(id){return uuids[id]},cancel:function(id){var onCancelRetVal=options.onCancel(id,api.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeFile(id)})}else if(onCancelRetVal!==false){expungeFile(id);return true}return false},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,api.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(id,iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove()}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,onUuidChange,logCallback){"use strict";var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.supportedFeatures.chunking,resumeEnabled=options.resume.enabled&&chunkFiles&&qq.supportedFeatures.resume,resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);if(fileState[id]){delete fileState[id].xhr}uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(id,xhr){var response;try{response=qq.parseJson(xhr.responseText);if(response.newUuid!==undefined){log("Server requested UUID change from '"+fileState[id].uuid+"' to '"+response.newUuid+"'");fileState[id].uuid=response.newUuid;onUuidChanged(id,response.newUuid)}}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(id,xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex){var currentChunkIndex;for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}uploadNextChunk(id)}function onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex);calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}function handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex){var name=api.getName(id),firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part),onResumeRetVal;onResumeRetVal=options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume));if(qq.isPromise(onResumeRetVal)){log("Waiting for onResume promise to be fulfilled for "+id);onResumeRetVal.then(function(){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)},function(){log("onResume promise fulfilled - failure indicated. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)})}else if(onResumeRetVal!==false){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)}else{log("onResume callback returned false. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}function handleFileChunkingUpload(id,retry){var firstChunkIndex=0,persistedChunkInfoForResume;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex)}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{uploadNextChunk(id)}}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}function expungeItem(id){var xhr=fileState[id].xhr;if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]}api={add:function(fileOrBlobData){var id,persistedChunkData,uuid=qq.getUniqueId();if(qq.isFile(fileOrBlobData)){id=fileState.push({file:fileOrBlobData})-1}else if(qq.isBlob(fileOrBlobData.blob)){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}if(resumeEnabled){persistedChunkData=getPersistedChunkData(id);if(persistedChunkData){uuid=persistedChunkData.uuid}}fileState[id].uuid=uuid;return id},getName:function(id){if(api.isValid(id)){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}}else{log(id+" is not a valid item ID.","error")}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},expunge:function(id){return expungeItem(id)},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);if(this.isValid(id)){options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}}},cancel:function(id){var onCancelRetVal=options.onCancel(id,this.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeItem(id)})}else if(onCancelRetVal!==false){expungeItem(id);return true}return false},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.2/iframe.xss.response.js b/ajax/libs/file-uploader/3.6.2/iframe.xss.response.js
new file mode 100644
index 000000000..e11fca1e5
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.2/iframe.xss.response.js
@@ -0,0 +1,6 @@
+(function() {
+ var match = /(\{.+\}).+/.exec(document.body.innerHTML);
+ if (match) {
+ parent.postMessage(match[1], '*');
+ }
+}());
diff --git a/ajax/libs/file-uploader/3.6.2/iframe.xss.response.min.js b/ajax/libs/file-uploader/3.6.2/iframe.xss.response.min.js
new file mode 100644
index 000000000..345cf3603
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.2/iframe.xss.response.min.js
@@ -0,0 +1 @@
+!function(){var match=/(\{.+\}).+/.exec(document.body.innerHTML);if(match){parent.postMessage(match[1],"*")}}();
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.2/loading.gif b/ajax/libs/file-uploader/3.6.2/loading.gif
new file mode 100644
index 000000000..6fba77609
Binary files /dev/null and b/ajax/libs/file-uploader/3.6.2/loading.gif differ
diff --git a/ajax/libs/file-uploader/3.6.2/processing.gif b/ajax/libs/file-uploader/3.6.2/processing.gif
new file mode 100644
index 000000000..7c99504e1
Binary files /dev/null and b/ajax/libs/file-uploader/3.6.2/processing.gif differ
diff --git a/ajax/libs/file-uploader/3.6.3/fineuploader-jquery.js b/ajax/libs/file-uploader/3.6.3/fineuploader-jquery.js
new file mode 100644
index 000000000..0a25e3cea
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.3/fineuploader-jquery.js
@@ -0,0 +1,5039 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: -unstable-
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity != null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable && !variable.nodeType && Object.prototype.toString.call(variable) === '[object Object]';
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isArray = function(variable) {
+ "use strict";
+ return Object.prototype.toString.call(variable) === "[object Array]";
+}
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFile = function(maybeFile) {
+ "use strict";
+
+ return window.File && Object.prototype.toString.call(maybeFile) === '[object File]'
+};
+
+qq.isFileList = function(maybeFileList) {
+ return window.FileList && Object.prototype.toString.call(maybeFileList) === '[object FileList]'
+}
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+
+ return qq.isFile(maybeFileOrInput) || qq.isInput(maybeFileOrInput);
+};
+
+qq.isInput = function(maybeInput) {
+ if (window.HTMLInputElement) {
+ if (Object.prototype.toString.call(maybeInput) === '[object HTMLInputElement]') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ if (maybeInput.tagName) {
+ if (maybeInput.tagName.toLowerCase() === 'input') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && Object.prototype.toString.call(maybeBlob) === '[object Blob]';
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice !== undefined || File.prototype.webkitSlice !== undefined || File.prototype.mozSlice !== undefined);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+
+ return first;
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+qq.ios = function() {
+ "use strict";
+ return navigator.userAgent.indexOf("iPad") !== -1
+ || navigator.userAgent.indexOf("iPod") !== -1
+ || navigator.userAgent.indexOf("iPhone") !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(objOrArray, callback) {
+ "use strict";
+ var keyOrIndex, retVal;
+ if (objOrArray) {
+ if (qq.isArray(objOrArray)) {
+ for (keyOrIndex = 0; keyOrIndex < objOrArray.length; keyOrIndex++) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ else {
+ for (keyOrIndex in objOrArray) {
+ if (Object.prototype.hasOwnProperty.call(objOrArray, keyOrIndex)) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+ }
+};
+
+//include any args that should be passed to the new function after the context arg
+qq.bind = function(oldFunc, context) {
+ if (qq.isFunction(oldFunc)) {
+ var args = Array.prototype.slice.call(arguments, 2);
+
+ return function() {
+ if (arguments.length) {
+ args = args.concat(Array.prototype.slice.call(arguments))
+ }
+ return oldFunc.apply(context, args);
+ };
+ }
+
+ throw new Error("first parameter must be a function!");
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ cookie;
+
+ qq.each(ca, function(idx, part) {
+ var cookiePart = part;
+ while (cookiePart.charAt(0)==' ') {
+ cookiePart = cookiePart.substring(1, cookiePart.length);
+ }
+
+ if (cookiePart.indexOf(nameEQ) === 0) {
+ cookie = cookiePart.substring(nameEQ.length, cookiePart.length);
+ return false;
+ }
+ });
+
+ return cookie;
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+qq.version="-unstable-";qq.supportedFeatures = (function() {
+ var supportsUploading,
+ supportsAjaxFileUploading,
+ supportsFolderDrop,
+ supportsChunking,
+ supportsResume,
+ supportsUploadViaPaste,
+ supportsUploadCors,
+ supportsDeleteFileCors;
+
+
+ function testSupportsFileInputElement() {
+ var supported = true,
+ tempInput;
+
+ try {
+ tempInput = document.createElement('input');
+ tempInput.type = 'file';
+ qq(tempInput).hide();
+
+ if(tempInput.disabled) {
+ supported = false;
+ }
+ }
+ catch(ex) {
+ supported = false;
+ }
+
+ return supported;
+ }
+
+ //only way to test for Filesystem API support since webkit does not expose the DataTransfer interface
+ function isChrome21OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[2][1-9]|Chrome\/[3-9][0-9]/) !== undefined;
+ }
+
+ //only way to test for complete Clipboard API support at this time
+ function isChrome14OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/) !== undefined;
+ }
+
+
+ supportsUploading = testSupportsFileInputElement();
+
+ supportsAjaxFileUploading = supportsUploading && qq.isXhrUploadSupported();
+
+ supportsFolderDrop = supportsAjaxFileUploading && isChrome21OrHigher();
+
+ supportsChunking = supportsAjaxFileUploading && qq.isFileChunkingSupported();
+
+ supportsResume = supportsAjaxFileUploading && supportsChunking && qq.areCookiesEnabled();
+
+ supportsUploadViaPaste = supportsAjaxFileUploading && isChrome14OrHigher();
+
+ supportsUploadCors = supportsUploading && (window.postMessage !== undefined || supportsAjaxFileUploading);
+
+ supportsDeleteFileCors = supportsAjaxFileUploading;
+
+
+ return {
+ uploading: supportsUploading,
+ ajaxUploading: supportsAjaxFileUploading,
+ fileDrop: supportsAjaxFileUploading, //NOTE: will also return true for touch-only devices. It's not currently possible to accurately test for touch-only devices
+ folderDrop: supportsFolderDrop,
+ chunking: supportsChunking,
+ resume: supportsResume,
+ uploadCustomHeaders: supportsAjaxFileUploading,
+ uploadNonMultipart: supportsAjaxFileUploading,
+ itemSizeValidation: supportsAjaxFileUploading,
+ uploadViaPaste: supportsUploadViaPaste,
+ progressBar: supportsAjaxFileUploading,
+ uploadCors: supportsUploadCors,
+ deleteFileCors: supportsDeleteFileCors,
+ canDetermineSize: supportsAjaxFileUploading
+ }
+
+}());
+/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallbacks = [],
+ failureCallbacks = [],
+ doneCallbacks = [],
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ if (onSuccess) {
+ successCallbacks.push(onSuccess);
+ }
+ if (onFailure) {
+ failureCallbacks.push(onFailure);
+ }
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+
+ return this;
+ },
+
+ done: function(callback) {
+ if (state === 0) {
+ doneCallbacks.push(callback);
+ }
+ else {
+ callback();
+ }
+
+ return this;
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallbacks.length) {
+ qq.each(successCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallbacks.length) {
+ qq.each(failureCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ }
+ };
+};
+
+qq.isPromise = function(maybePromise) {
+ return maybePromise && maybePromise.then && maybePromise.done;
+};/*globals qq*/
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ disposeSupport = new qq.DisposeSupport(),
+ options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input) {},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+
+ qq.extend(options, o);
+
+ // make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};qq.UploadData = function(uploaderProxy) {
+ var data = [],
+ byId = {},
+ byUuid = {},
+ byStatus = {},
+ api;
+
+ function getDataByIds(ids) {
+ if (qq.isArray(ids)) {
+ var entries = [];
+
+ qq.each(ids, function(idx, id) {
+ entries.push(data[byId[id]]);
+ });
+
+ return entries;
+ }
+
+ return data[byId[ids]];
+ }
+
+ function getDataByUuids(uuids) {
+ if (qq.isArray(uuids)) {
+ var entries = [];
+
+ qq.each(uuids, function(idx, uuid) {
+ entries.push(data[byUuid[uuid]]);
+ });
+
+ return entries;
+ }
+
+ return data[byUuid[uuids]];
+ }
+
+ function getDataByStatus(status) {
+ var statusResults = [],
+ statuses = [].concat(status);
+
+ qq.each(statuses, function(index, statusEnum) {
+ var statusResultIndexes = byStatus[statusEnum];
+
+ if (statusResultIndexes !== undefined) {
+ qq.each(statusResultIndexes, function(i, dataIndex) {
+ statusResults.push(data[dataIndex]);
+ });
+ }
+ });
+
+ return statusResults;
+ }
+
+ api = {
+ added: function(id) {
+ var uuid = uploaderProxy.getUuid(id),
+ name = uploaderProxy.getName(id),
+ size = uploaderProxy.getSize(id),
+ status = qq.status.SUBMITTING;
+
+ var index = data.push({
+ id: id,
+ name: name,
+ uuid: uuid,
+ size: size,
+ status: status
+ }) - 1;
+
+ byId[id] = index;
+
+ byUuid[uuid] = index;
+
+ if (byStatus[status] === undefined) {
+ byStatus[status] = [];
+ }
+ byStatus[status].push(index);
+
+ uploaderProxy.onStatusChange(id, undefined, status);
+ },
+
+ retrieve: function(optionalFilter) {
+ if (qq.isObject(optionalFilter) && data.length) {
+ if (optionalFilter.id !== undefined) {
+ return getDataByIds(optionalFilter.id);
+ }
+
+ else if (optionalFilter.uuid !== undefined) {
+ return getDataByUuids(optionalFilter.uuid);
+ }
+
+ else if (optionalFilter.status) {
+ return getDataByStatus(optionalFilter.status);
+ }
+ }
+ else {
+ return qq.extend([], data, true);
+ }
+ },
+
+ reset: function() {
+ data = [];
+ byId = {};
+ byUuid = {};
+ byStatus = {};
+ },
+
+ setStatus: function(id, newStatus) {
+ var dataIndex = byId[id],
+ oldStatus = data[dataIndex].status,
+ byStatusOldStatusIndex = qq.indexOf(byStatus[oldStatus], dataIndex);
+
+ byStatus[oldStatus].splice(byStatusOldStatusIndex, 1);
+
+ data[dataIndex].status = newStatus;
+
+ if (byStatus[newStatus] === undefined) {
+ byStatus[newStatus] = [];
+ }
+ byStatus[newStatus].push(dataIndex);
+
+ uploaderProxy.onStatusChange(id, oldStatus, newStatus);
+ },
+
+ uuidChanged: function(id, newUuid) {
+ var dataIndex = byId[id],
+ oldUuid = data[dataIndex].uuid;
+
+ data[dataIndex].uuid = newUuid;
+ byUuid[newUuid] = dataIndex;
+ delete byUuid[oldUuid];
+ }
+ };
+
+ return api;
+};
+
+qq.status = {
+ SUBMITTING: "submitting",
+ SUBMITTED: "submitted",
+ REJECTED: "rejected",
+ QUEUED: "queued",
+ CANCELED: "canceled",
+ UPLOADING: "uploading",
+ UPLOAD_RETRYING: "retrying upload",
+ UPLOAD_SUCCESSFUL: "upload successful",
+ UPLOAD_FAILED: "upload failed",
+ DELETE_FAILED: "delete failed",
+ DELETING: "deleting",
+ DELETED: "deleted"
+};qq.FineUploaderBasic = function(o) {
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true,
+ acceptFiles: null
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON, maybeXhr){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){},
+ onPasteReceived: function(blob) {},
+ onStatusChange: function(id, oldStatus, newStatus) {}
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ defaultResponseError: "Upload failure reason unknown",
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'misc_data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ },
+ camera: {
+ ios: false
+ }
+ };
+
+ qq.extend(this._options, o, true);
+
+ this._handleCameraAccess();
+
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData = this._createUploadDataTracker();
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader ' + qq.version + '] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader ' + qq.version + '] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function() {
+ return this._filesInProgress.length;
+ },
+ getNetUploads: function() {
+ return this._netUploaded;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netUploadedOrQueued++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData.reset();
+
+ if (this._pasteHandler) {
+ this._pasteHandler.reset();
+ }
+ },
+ addFiles: function(filesOrInputs, params, endpoint) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ fileOrInputIndex, fileOrInput, fileIndex;
+
+ if (filesOrInputs) {
+ if (!qq.isFileList(filesOrInputs)) {
+ filesOrInputs = [].concat(filesOrInputs);
+ }
+
+ for (fileOrInputIndex = 0; fileOrInputIndex < filesOrInputs.length; fileOrInputIndex+=1) {
+ fileOrInput = filesOrInputs[fileOrInputIndex];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ if (qq.isInput(fileOrInput) && qq.supportedFeatures.ajaxUploading) {
+ for (fileIndex = 0; fileIndex < fileOrInput.files.length; fileIndex++) {
+ verifiedFilesOrInputs.push(fileOrInput.files[fileIndex]);
+ }
+ }
+ else {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Received ' + verifiedFilesOrInputs.length + ' files or inputs.');
+ this._prepareItemsForUpload(verifiedFilesOrInputs, params, endpoint);
+ }
+ },
+ addBlobs: function(blobDataOrArray, params, endpoint) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._prepareItemsForUpload(verifiedBlobDataList, params, endpoint);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ doesExist: function(fileOrBlobId) {
+ return this._handler.isValid(fileOrBlobId);
+ },
+ getUploads: function(optionalFilter) {
+ return this._uploadData.retrieve(optionalFilter);
+ },
+ _handleCheckedCallback: function(details) {
+ var self = this,
+ callbackRetVal = details.callback();
+
+ if (qq.isPromise(callbackRetVal)) {
+ this.log(details.name + " - waiting for " + details.name + " promise to be fulfilled for " + details.identifier);
+ return callbackRetVal.then(
+ function(successParam) {
+ self.log(details.name + " promise success for " + details.identifier);
+ details.onSuccess(successParam);
+ },
+ function() {
+ if (details.onFailure) {
+ self.log(details.name + " promise failure for " + details.identifier);
+ details.onFailure();
+ }
+ else {
+ self.log(details.name + " promise failure for " + details.identifier);
+ }
+ });
+ }
+
+ if (callbackRetVal !== false) {
+ details.onSuccess(callbackRetVal);
+ }
+ else {
+ if (details.onFailure) {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Invoking failure callback.")
+ details.onFailure();
+ }
+ else {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Will not proceed.")
+ }
+ }
+
+ return callbackRetVal;
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.supportedFeatures.ajaxUploading,
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result, xhr);
+ },
+ onCancel: function(id, name) {
+ return self._handleCheckedCallback({
+ name: "onCancel",
+ callback: qq.bind(self._options.callbacks.onCancel, self, id, name),
+ onSuccess: qq.bind(self._onCancel, self, id, name),
+ identifier: id
+ });
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ onUuidChanged: function(id, newUuid) {
+ self._uploadData.uuidChanged(id, newUuid);
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ self._handleCheckedCallback({
+ name: "onPasteReceived",
+ callback: qq.bind(self._options.callbacks.onPasteReceived, self, blob),
+ onSuccess: qq.bind(self._handlePasteSuccess, self, blob),
+ identifier: "pasted image"
+ });
+ }
+ }
+ });
+ },
+ _createUploadDataTracker: function() {
+ var self = this;
+
+ return new qq.UploadData({
+ getName: function(id) {
+ return self.getName(id);
+ },
+ getUuid: function(id) {
+ return self.getUuid(id);
+ },
+ getSize: function(id) {
+ return self.getSize(id);
+ },
+ onStatusChange: function(id, oldStatus, newStatus) {
+ self._options.callbacks.onStatusChange(id, oldStatus, newStatus);
+ }
+ });
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total) {
+ //nothing to do yet in core uploader
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_FAILED);
+ this._netUploadedOrQueued--;
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_SUCCESSFUL);
+ this._netUploaded++;
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.CANCELED);
+
+ this._netUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected || qq.supportedFeatures.deleteFileCors));
+ },
+ _onSubmitDelete: function(id, onSuccessCallback) {
+ if (this._isDeletePossible()) {
+ return this._handleCheckedCallback({
+ name: "onSubmitDelete",
+ callback: qq.bind(this._options.callbacks.onSubmitDelete, this, id),
+ onSuccess: onSuccessCallback || qq.bind(this._deleteHandler.sendDelete, this, id, this.getUuid(id)),
+ identifier: id
+ });
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(id) {
+ this._uploadData.setStatus(id, qq.status.DELETING);
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this._uploadData.setStatus(id, qq.status.DELETE_FAILED);
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.DELETED);
+ this._netUploadedOrQueued--;
+ this._netUploaded--;
+ this._handler.expunge(id);
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.UPLOADING);
+ },
+ _onInputChange: function(input){
+ if (qq.supportedFeatures.ajaxUploading) {
+ this.addFiles(input.files);
+ }
+ else {
+ this.addFiles(input);
+ }
+
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems", "");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : this._options.text.defaultResponseError;
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ var validationDescriptors = this._getValidationDescriptors(items);
+
+ this._handleCheckedCallback({
+ name: "onValidateBatch",
+ callback: qq.bind(this._options.callbacks.onValidateBatch, this, validationDescriptors),
+ onSuccess: qq.bind(this._onValidateBatchCallbackSuccess, this, validationDescriptors, items, params, endpoint),
+ identifier: "batch validation"
+ });
+ },
+ _upload: function(blobOrFileContainer, params, endpoint) {
+ var id = this._handler.add(blobOrFileContainer),
+ name = this._handler.getName(id);
+
+ this._uploadData.added(id);
+
+ if (params) {
+ this.setParams(params, id);
+ }
+
+ if (endpoint) {
+ this.setEndpoint(endpoint, id);
+ }
+
+ this._handleCheckedCallback({
+ name: "onSubmit",
+ callback: qq.bind(this._options.callbacks.onSubmit, this, id, name),
+ onSuccess: qq.bind(this._onSubmitCallbackSuccess, this, id, name),
+ onFailure: qq.bind(this._fileOrBlobRejected, this, id, name),
+ identifier: id
+ });
+ },
+ _onSubmitCallbackSuccess: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.SUBMITTED);
+
+ this._onSubmit(id, name);
+ this._options.callbacks.onSubmitted(id, name);
+
+ if (this._options.autoUpload) {
+ if (!this._handler.upload(id)) {
+ this._uploadData.setStatus(id, qq.status.QUEUED);
+ }
+ }
+ else {
+ this._storeForLater(id);
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _onValidateBatchCallbackSuccess: function(validationDescriptors, items, params, endpoint) {
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netUploadedOrQueued + validationDescriptors.length;
+
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ if (items.length > 0) {
+ this._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(this._options.callbacks.onValidate, this, items[0]),
+ onSuccess: qq.bind(this._onValidateCallbackSuccess, this, items, 0, params, endpoint),
+ onFailure: qq.bind(this._onValidateCallbackFailure, this, items, 0, params, endpoint),
+ identifier: "Item '" + items[0].name + "', size: " + items[0].size
+ });
+ }
+ else {
+ this._itemError("noFilesError", "");
+ }
+ }
+ else {
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ },
+ _onValidateCallbackSuccess: function(items, index, params, endpoint) {
+ var nextIndex = index+1,
+ validationDescriptor = this._getValidationDescriptor(items[index]),
+ validItem = false;
+
+ if (this._validateFileOrBlobData(items[index], validationDescriptor)) {
+ validItem = true;
+ this._upload(items[index], params, endpoint);
+ }
+
+ this._maybeProcessNextItemAfterOnValidateCallback(validItem, items, nextIndex, params, endpoint);
+ },
+ _onValidateCallbackFailure: function(items, index, params, endpoint) {
+ var nextIndex = index+ 1;
+
+ this._fileOrBlobRejected(undefined, items[0].name);
+
+ this._maybeProcessNextItemAfterOnValidateCallback(false, items, nextIndex, params, endpoint);
+ },
+ _maybeProcessNextItemAfterOnValidateCallback: function(validItem, items, index, params, endpoint) {
+ var self = this;
+
+ if (items.length > index) {
+ if (validItem || !this._options.validation.stopOnFirstInvalidFile) {
+ //use setTimeout to prevent a stack overflow with a large number of files in the batch & non-promissory callbacks
+ setTimeout(function() {
+ var validationDescriptor = self._getValidationDescriptor(items[index]);
+
+ self._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(self._options.callbacks.onValidate, self, items[index]),
+ onSuccess: qq.bind(self._onValidateCallbackSuccess, self, items, index, params, endpoint),
+ onFailure: qq.bind(self._onValidateCallbackFailure, self, items, index, params, endpoint),
+ identifier: "Item '" + validationDescriptor.name + "', size: " + validationDescriptor.size
+ });
+ }, 0);
+ }
+ }
+ },
+ _validateFileOrBlobData: function(item, validationDescriptor) {
+ var name = validationDescriptor.name,
+ size = validationDescriptor.size,
+ valid = true;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ valid = false;
+ }
+
+ if (qq.isFileOrInput(item) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ valid = false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ valid = false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ valid = false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ valid = false;
+ }
+
+ if (!valid) {
+ this._fileOrBlobRejected(undefined, name);
+ }
+
+ return valid;
+ },
+ _fileOrBlobRejected: function(id, name) {
+ if (id !== undefined) {
+ this._uploadData.setStatus(id, qq.status.REJECTED);
+ }
+ },
+ _itemError: function(code, nameOrNames) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ names = [].concat(nameOrNames),
+ name = names[0],
+ extensionsForMessage, placeholderMatch;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ placeholderMatch = message.match(/(\{\w+\})/g);
+ if (placeholderMatch !== null) {
+ qq.each(placeholderMatch, function(idx, placeholder) {
+ r(placeholder, names[idx]);
+ });
+ }
+
+ this._options.callbacks.onError(null, name, message, undefined);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message, undefined);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1000;
+ i++;
+ } while (bytes > 999);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size !== undefined) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ },
+ _handleCameraAccess: function() {
+ if (this._options.camera.ios && qq.ios()) {
+ this._options.multiple = false;
+
+ if (this._options.validation.acceptFiles === null) {
+ this._options.validation.acceptFiles = "image/*;capture=camera";
+ }
+ else {
+ this._options.validation.acceptFiles += ",image/*;capture=camera";
+ }
+ }
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz,
+ droppedFiles = [],
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropZoneElements: [],
+ hideDropZonesBeforeEnter: false,
+ allowMultipleItems: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: new qq.DragAndDrop.callbacks()
+ };
+
+ qq.extend(options, o, true);
+
+ setupDragDrop();
+
+ function uploadDroppedFiles(files) {
+ options.callbacks.dropLog('Grabbed ' + files.length + " dropped files.");
+ dz.dropDisabled(false);
+ options.callbacks.processingDroppedFilesComplete(files);
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i,
+ parseEntryPromise = new qq.Promise();
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ droppedFiles.push(file);
+ parseEntryPromise.success();
+ },
+ function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+ else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ var entriesLeft = entries.length;
+
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]).done(function() {
+ entriesLeft-=1;
+
+ if (entriesLeft === 0) {
+ parseEntryPromise.success();
+ }
+ });
+ }
+
+ if (!entries.length) {
+ parseEntryPromise.success();
+ }
+ }, function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+
+ return parseEntryPromise;
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry,
+ pendingFolderPromises = [],
+ handleDataTransferPromise = new qq.Promise();
+
+ options.callbacks.processingDroppedFiles();
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.allowMultipleItems) {
+ options.callbacks.processingDroppedFilesComplete([]);
+ options.callbacks.dropError('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ handleDataTransferPromise.failure();
+ }
+ else {
+ droppedFiles = [];
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ }
+
+ else {
+ pendingFolderPromises.push(traverseFileTree(entry).done(function() {
+ pendingFolderPromises.pop();
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }));
+ }
+ }
+ }
+ }
+ else {
+ droppedFiles = dataTransfer.files;
+ }
+
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }
+
+ return handleDataTransferPromise;
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer).done(function() {
+ uploadDroppedFiles(droppedFiles);
+ });
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ var dropZones = options.dropZoneElements;
+
+ qq.each(dropZones, function(idx, dropZone) {
+ setupDropzone(dropZone);
+ })
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (dropZones.length && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).css({display: 'block'});
+ });
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropZonesBeforeEnter && qq.FineUploader.prototype._leaving_document_out(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setupExtraDropzone: function(element) {
+ options.dropZoneElements.push(element);
+ setupDropzone(element);
+ },
+
+ removeDropzone: function(element) {
+ var i,
+ dzs = options.dropZoneElements;
+
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+qq.DragAndDrop.callbacks = function() {
+ return {
+ processingDroppedFiles: function() {},
+ processingDroppedFilesComplete: function(files) {},
+ dropError: function(code, errorSpecifics) {
+ qq.log("Drag & drop error code '" + code + " with these specifics: '" + errorSpecifics + "'", "error");
+ },
+ dropLog: function(message, level) {
+ qq.log(message, level);
+ }
+ }
+}
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file",
+ unsupportedBrowser: "Unrecoverable error - this browser does not permit file uploading of any kind."
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false,
+ prependFiles: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+
+ if (!qq.supportedFeatures.uploading || (this._options.cors.expected && !qq.supportedFeatures.uploadCors)) {
+ this._options.element.innerHTML = "" + this._options.messages.unsupportedBrowser + "
"
+ }
+ else {
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dropZoneElements = this._options.dragAndDrop.extraDropzones,
+ preventSelectFiles;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ dropZoneElements.push(this._find(this._options.element, 'drop'));
+ }
+
+ return new qq.DragAndDrop({
+ dropZoneElements: dropZoneElements,
+ hideDropZonesBeforeEnter: this._options.dragAndDrop.hideDropzones,
+ allowMultipleItems: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ processingDroppedFiles: function() {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ },
+ processingDroppedFilesComplete: function(files) {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ dropError: function(code, errorData) {
+ self._itemError(code, errorData);
+ },
+ dropLog: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type) {
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name) {
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ var onSuccessCallback = qq.bind(this._onSubmitDeleteSuccess, this, id);
+
+ qq.FineUploaderBasic.prototype._onSubmitDelete.call(this, id, onSuccessCallback);
+ },
+ _onSubmitDeleteSuccess: function(id) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ if (this._options.display.prependFiles) {
+ this._prependItem(item);
+ }
+ else {
+ this._listElement.appendChild(item);
+ }
+ this._filesInBatchAddedToUi += 1;
+
+ if (this._options.display.fileSizeOnSubmit && qq.supportedFeatures.ajaxUploading) {
+ this._displayFileSize(id);
+ }
+ },
+ _prependItem: function(item) {
+ var parentEl = this._listElement,
+ beforeEl = parentEl.firstChild;
+
+ if (this._totalFilesInBatch > 1 && this._filesInBatchAddedToUi > 0) {
+ beforeEl = qq(parentEl).children()[this._filesInBatchAddedToUi - 1].nextSibling;
+
+ }
+
+ parentEl.insertBefore(item, beforeEl);
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = item.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ },
+ _fileOrBlobRejected: function(id, name) {
+ this._totalFilesInBatch -= 1;
+ qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this, arguments);
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ this._totalFilesInBatch = items.length;
+ this._filesInBatchAddedToUi = 0;
+ qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this, arguments);
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, handlerImpl, api;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){},
+ onUuidChanged: function(id, newUuid){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.supportedFeatures.ajaxUploading) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, options.onUuidChanged, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, options.onUuidChanged, log);
+ }
+
+ function cancelSuccess(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ dequeue(id);
+ }
+
+
+ api = {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ handlerImpl.upload(id);
+ return true;
+ }
+
+ return false;
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ var cancelRetVal = handlerImpl.cancel(id);
+
+ if (qq.isPromise(cancelRetVal)) {
+ cancelRetVal.then(function() {
+ cancelSuccess(id);
+ });
+ }
+ else if (cancelRetVal !== false) {
+ cancelSuccess(id);
+ }
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ api.cancelAll();
+ queue = [];
+ handlerImpl.reset();
+ },
+ expunge: function(id) {
+ return handlerImpl.expunge(id);
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, onUuidChange, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ formHandlerInstanceId = qq.getUniqueId(),
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var iframeName = iframe.id,
+ fileId = getFileIdForIframeName(iframeName);
+
+ onloadCallbacks[uuids[fileId]] = callback;
+
+ detachLoadEvents[fileId] = qq(iframe).attach('load', function() {
+ if (inputs[fileId]) {
+ log("Received iframe load event for CORS upload request (iframe name " + iframeName + ")");
+
+ postMessageCallbackTimers[iframeName] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for iframe name " + iframeName;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(iframeName, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = parseResponse(getFileIdForIframeName(iframeName), message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ log("Handling response for iframe name " + iframeName);
+ clearTimeout(postMessageCallbackTimers[iframeName]);
+ delete postMessageCallbackTimers[iframeName];
+
+ detachLoadEvent(iframeName);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(iframeName);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(id, iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHtml = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHtml);
+ //plain text response may be wrapped in tag
+ if (innerHtml && innerHtml.match(/^ ');
+
+ iframe.setAttribute('id', iframeName);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+ function expungeFile(id) {
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(getIframeName(id));
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ }
+
+ function getFileIdForIframeName(iframeName) {
+ return iframeName.split("_")[0];
+ }
+
+ function getIframeName(fileId) {
+ return fileId + "_" + formHandlerInstanceId;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ formHandlerInstanceId = qq.getUniqueId();
+ },
+ expunge: function(id) {
+ return expungeFile(id);
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, api.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeFile(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeFile(id);
+ return true;
+ }
+
+ return false;
+ },
+
+ upload: function(id) {
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, api.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(id, iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, onUuidChange, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.supportedFeatures.chunking,
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.supportedFeatures.resume,
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+ options.onComplete(id, name, response, xhr);
+
+ if (fileState[id]) {
+ delete fileState[id].xhr;
+ }
+
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(id, xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+
+ if (response.newUuid !== undefined) {
+ log("Server requested UUID change from '" + fileState[id].uuid + "' to '" + response.newUuid + "'");
+ fileState[id].uuid = response.newUuid;
+ onUuidChanged(id, response.newUuid);
+ }
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(id, xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex) {
+ var currentChunkIndex;
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+
+ function handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex) {
+ var name = api.getName(id),
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part),
+ onResumeRetVal;
+
+ onResumeRetVal = options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume));
+ if (qq.isPromise(onResumeRetVal)) {
+ log("Waiting for onResume promise to be fulfilled for " + id);
+ onResumeRetVal.then(
+ function() {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ },
+ function() {
+ log("onResume promise fulfilled - failure indicated. Will not resume.")
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ );
+ }
+ else if (onResumeRetVal !== false) {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ }
+ else {
+ log("onResume callback returned false. Will not resume.");
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var firstChunkIndex = 0,
+ persistedChunkInfoForResume;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex);
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ uploadNextChunk(id);
+ }
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+ function expungeItem(id) {
+ var xhr = fileState[id].xhr;
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ }
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id, persistedChunkData,
+ uuid = qq.getUniqueId();
+
+ if (qq.isFile(fileOrBlobData)) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (qq.isBlob(fileOrBlobData.blob)) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ if (resumeEnabled) {
+ persistedChunkData = getPersistedChunkData(id);
+
+ if (persistedChunkData) {
+ uuid = persistedChunkData.uuid;
+ }
+ }
+
+ fileState[id].uuid = uuid;
+
+ return id;
+ },
+ getName: function(id){
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ expunge: function(id) {
+ return expungeItem(id);
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry) {
+ var name = this.getName(id);
+
+ if (this.isValid(id)) {
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ }
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, this.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeItem(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeItem(id);
+ return true;
+ }
+
+ return false;
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var uploader, $el, init, dataStore, pluginOption, pluginOptions, addCallbacks, transformVariables, isValidCommand,
+ delegateCommand;
+
+ pluginOptions = ['uploaderType'];
+
+ init = function (options) {
+ if (options) {
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+
+ if (pluginOption('uploaderType') === 'basic') {
+ uploader(new qq.FineUploaderBasic(xformedOpts));
+ }
+ else {
+ uploader(new qq.FineUploader(xformedOpts));
+ }
+ }
+
+ return $el;
+ };
+
+ dataStore = function(key, val) {
+ var data = $el.data('fineuploader');
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data('fineuploader', data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ //the underlying Fine Uploader instance is stored in jQuery's data stored, associated with the element
+ // tied to this instance of the plug-in
+ uploader = function(instanceToStore) {
+ return dataStore('uploader', instanceToStore);
+ };
+
+ pluginOption = function(option, optionVal) {
+ return dataStore(option, optionVal);
+ };
+
+ //implement all callbacks defined in Fine Uploader as functions that trigger appropriately names events and
+ // return the result of executing the bound handler back to Fine Uploader
+ addCallbacks = function(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {},
+ uploaderInst = new qq.FineUploaderBasic();
+
+ $.each(uploaderInst._options.callbacks, function(prop, func) {
+ var name, $callbackEl;
+
+ name = /^on(\w+)/.exec(prop)[1];
+ name = name.substring(0, 1).toLowerCase() + name.substring(1);
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var args = Array.prototype.slice.call(arguments);
+
+ return $callbackEl.triggerHandler(name, args);
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ transformVariables = function(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ if (source.uploaderType !== 'basic') {
+ xformed = { element : $el[0] };
+ }
+ else {
+ xformed = {};
+ }
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if ($.inArray(prop, pluginOptions) >= 0) {
+ pluginOption(prop, val);
+ }
+ else if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ isValidCommand = function(command) {
+ return $.type(command) === "string" &&
+ !command.match(/^_/) && //enforce private methods convention
+ uploader()[command] !== undefined;
+ };
+
+ //assuming we have already verified that this is a valid command, call the associated function in the underlying
+ // Fine Uploader instance (passing along the arguments from the caller) and return the result of the call back to the caller
+ delegateCommand = function(command) {
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
+
+ transformVariables(origArgs, xformedArgs);
+
+ return uploader()[command].apply(uploader(), xformedArgs);
+ };
+
+ $.fn.fineUploader = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (uploader() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error('Method ' + optionsOrCommand + ' does not exist on jQuery.fineUploader');
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
+/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var rootDataKey = "fineUploaderDnd",
+ $el;
+
+ function init (options) {
+ if (!options) {
+ options = {};
+ }
+
+ options.dropZoneElements = [$el];
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+ dnd(new qq.DragAndDrop(xformedOpts));
+
+ return $el;
+ };
+
+ function dataStore(key, val) {
+ var data = $el.data(rootDataKey);
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data(rootDataKey, data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ function dnd(instanceToStore) {
+ return dataStore('dndInstance', instanceToStore);
+ };
+
+ function addCallbacks(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {},
+ dndInst = new qq.FineUploaderBasic();
+
+ $.each(new qq.DragAndDrop.callbacks(), function(prop, func) {
+ var name = prop,
+ $callbackEl;
+
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var args = Array.prototype.slice.call(arguments),
+ jqueryHandlerResult = $callbackEl.triggerHandler(name, args);
+
+ return jqueryHandlerResult;
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ function transformVariables(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ xformed = {};
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ function isValidCommand(command) {
+ return $.type(command) === "string" &&
+ command === "dispose" &&
+ dnd()[command] !== undefined;
+ };
+
+ function delegateCommand(command) {
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
+ transformVariables(origArgs, xformedArgs);
+ return dnd()[command].apply(dnd(), xformedArgs);
+ };
+
+ $.fn.fineUploaderDnd = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (dnd() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error("Method " + optionsOrCommand + " does not exist in Fine Uploader's DnD module.");
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
diff --git a/ajax/libs/file-uploader/3.6.3/fineuploader-jquery.min.js b/ajax/libs/file-uploader/3.6.3/fineuploader-jquery.min.js
new file mode 100644
index 000000000..08419b561
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.3/fineuploader-jquery.min.js
@@ -0,0 +1,16 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: 3.6.3
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!=null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable&&!variable.nodeType&&Object.prototype.toString.call(variable)==="[object Object]"};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isArray=function(variable){"use strict";return Object.prototype.toString.call(variable)==="[object Array]"};qq.isString=function(maybeString){"use strict";return Object.prototype.toString.call(maybeString)==="[object String]"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFile=function(maybeFile){"use strict";return window.File&&Object.prototype.toString.call(maybeFile)==="[object File]"};qq.isFileList=function(maybeFileList){return window.FileList&&Object.prototype.toString.call(maybeFileList)==="[object FileList]"};qq.isFileOrInput=function(maybeFileOrInput){"use strict";return qq.isFile(maybeFileOrInput)||qq.isInput(maybeFileOrInput)};qq.isInput=function(maybeInput){if(window.HTMLInputElement){if(Object.prototype.toString.call(maybeInput)==="[object HTMLInputElement]"){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}if(maybeInput.tagName){if(maybeInput.tagName.toLowerCase()==="input"){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&Object.prototype.toString.call(maybeBlob)==="[object Blob]"};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice!==undefined||File.prototype.webkitSlice!==undefined||File.prototype.mozSlice!==undefined)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}});return first};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{defaultResponseError:"Upload failure reason unknown",sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"misc_data",paramNames:{name:"qqblobname"}},paste:{targetElement:null,defaultName:"pasted_image"},camera:{ios:false}};qq.extend(this._options,o,true);this._handleCameraAccess();this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData=this._createUploadDataTracker();this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}if(this._options.paste.targetElement){this._pasteHandler=this._createPasteHandler()}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader "+qq.version+"] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader "+qq.version+"] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},getNetUploads:function(){return this._netUploaded},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._netUploadedOrQueued++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset();this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData.reset();if(this._pasteHandler){this._pasteHandler.reset()}},addFiles:function(filesOrInputs,params,endpoint){var self=this,verifiedFilesOrInputs=[],fileOrInputIndex,fileOrInput,fileIndex;if(filesOrInputs){if(!qq.isFileList(filesOrInputs)){filesOrInputs=[].concat(filesOrInputs)}for(fileOrInputIndex=0;fileOrInputIndex=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||qq.supportedFeatures.deleteFileCors)},_onSubmitDelete:function(id,onSuccessCallback){if(this._isDeletePossible()){return this._handleCheckedCallback({name:"onSubmitDelete",callback:qq.bind(this._options.callbacks.onSubmitDelete,this,id),onSuccess:onSuccessCallback||qq.bind(this._deleteHandler.sendDelete,this,id,this.getUuid(id)),identifier:id})}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(id){this._uploadData.setStatus(id,qq.status.DELETING)},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);
+if(isError){this._uploadData.setStatus(id,qq.status.DELETE_FAILED);this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status,xhr)}else{this._uploadData.setStatus(id,qq.status.DELETED);this._netUploadedOrQueued--;this._netUploaded--;this._handler.expunge(id);this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){this._uploadData.setStatus(id,qq.status.UPLOADING)},_onInputChange:function(input){if(qq.supportedFeatures.ajaxUploading){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0&&this._netUploadedOrQueued+1>itemLimit){this._itemError("retryFailTooManyItems","");return false}this.log("Retrying upload for '"+fileName+"' (id: "+id+")...");this._filesInProgress.push(id);return true}else{this.log("'"+id+"' is not a valid file ID","error");return false}},_maybeParseAndSendUploadError:function(id,name,response,xhr){if(!response.success){if(xhr&&xhr.status!==200&&!response.error){this._options.callbacks.onError(id,name,"XHR returned response code "+xhr.status,xhr)}else{var errorReason=response.error?response.error:this._options.text.defaultResponseError;this._options.callbacks.onError(id,name,errorReason,xhr)}}},_prepareItemsForUpload:function(items,params,endpoint){var validationDescriptors=this._getValidationDescriptors(items);this._handleCheckedCallback({name:"onValidateBatch",callback:qq.bind(this._options.callbacks.onValidateBatch,this,validationDescriptors),onSuccess:qq.bind(this._onValidateBatchCallbackSuccess,this,validationDescriptors,items,params,endpoint),identifier:"batch validation"})},_upload:function(blobOrFileContainer,params,endpoint){var id=this._handler.add(blobOrFileContainer),name=this._handler.getName(id);this._uploadData.added(id);if(params){this.setParams(params,id)}if(endpoint){this.setEndpoint(endpoint,id)}this._handleCheckedCallback({name:"onSubmit",callback:qq.bind(this._options.callbacks.onSubmit,this,id,name),onSuccess:qq.bind(this._onSubmitCallbackSuccess,this,id,name),onFailure:qq.bind(this._fileOrBlobRejected,this,id,name),identifier:id})},_onSubmitCallbackSuccess:function(id,name){this._uploadData.setStatus(id,qq.status.SUBMITTED);this._onSubmit(id,name);this._options.callbacks.onSubmitted(id,name);if(this._options.autoUpload){if(!this._handler.upload(id)){this._uploadData.setStatus(id,qq.status.QUEUED)}}else{this._storeForLater(id)}},_storeForLater:function(id){this._storedIds.push(id)},_onValidateBatchCallbackSuccess:function(validationDescriptors,items,params,endpoint){var errorMessage,itemLimit=this._options.validation.itemLimit,proposedNetFilesUploadedOrQueued=this._netUploadedOrQueued+validationDescriptors.length;if(itemLimit===0||proposedNetFilesUploadedOrQueued<=itemLimit){if(items.length>0){this._handleCheckedCallback({name:"onValidate",callback:qq.bind(this._options.callbacks.onValidate,this,items[0]),onSuccess:qq.bind(this._onValidateCallbackSuccess,this,items,0,params,endpoint),onFailure:qq.bind(this._onValidateCallbackFailure,this,items,0,params,endpoint),identifier:"Item '"+items[0].name+"', size: "+items[0].size})}else{this._itemError("noFilesError","")}}else{errorMessage=this._options.messages.tooManyItemsError.replace(/\{netItems\}/g,proposedNetFilesUploadedOrQueued).replace(/\{itemLimit\}/g,itemLimit);this._batchError(errorMessage)}},_onValidateCallbackSuccess:function(items,index,params,endpoint){var nextIndex=index+1,validationDescriptor=this._getValidationDescriptor(items[index]),validItem=false;if(this._validateFileOrBlobData(items[index],validationDescriptor)){validItem=true;this._upload(items[index],params,endpoint)}this._maybeProcessNextItemAfterOnValidateCallback(validItem,items,nextIndex,params,endpoint)},_onValidateCallbackFailure:function(items,index,params,endpoint){var nextIndex=index+1;this._fileOrBlobRejected(undefined,items[0].name);this._maybeProcessNextItemAfterOnValidateCallback(false,items,nextIndex,params,endpoint)},_maybeProcessNextItemAfterOnValidateCallback:function(validItem,items,index,params,endpoint){var self=this;if(items.length>index){if(validItem||!this._options.validation.stopOnFirstInvalidFile){setTimeout(function(){var validationDescriptor=self._getValidationDescriptor(items[index]);self._handleCheckedCallback({name:"onValidate",callback:qq.bind(self._options.callbacks.onValidate,self,items[index]),onSuccess:qq.bind(self._onValidateCallbackSuccess,self,items,index,params,endpoint),onFailure:qq.bind(self._onValidateCallbackFailure,self,items,index,params,endpoint),identifier:"Item '"+validationDescriptor.name+"', size: "+validationDescriptor.size})},0)}}},_validateFileOrBlobData:function(item,validationDescriptor){var name=validationDescriptor.name,size=validationDescriptor.size,valid=true;if(this._options.callbacks.onValidate(validationDescriptor)===false){valid=false}if(qq.isFileOrInput(item)&&!this._isAllowedExtension(name)){this._itemError("typeError",name);valid=false}else if(size===0){this._itemError("emptyError",name);valid=false}else if(size&&this._options.validation.sizeLimit&&size>this._options.validation.sizeLimit){this._itemError("sizeError",name);valid=false}else if(size&&size999);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size!==undefined){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}},_handleCameraAccess:function(){if(this._options.camera.ios&&qq.ios()){this._options.multiple=false;if(this._options.validation.acceptFiles===null){this._options.validation.acceptFiles="image/*;capture=camera"}else{this._options.validation.acceptFiles+=",image/*;capture=camera"}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,droppedFiles=[],disposeSupport=new qq.DisposeSupport;options={dropZoneElements:[],hideDropZonesBeforeEnter:false,allowMultipleItems:true,classes:{dropActive:null},callbacks:new qq.DragAndDrop.callbacks};qq.extend(options,o,true);setupDragDrop();function uploadDroppedFiles(files){options.callbacks.dropLog("Grabbed "+files.length+" dropped files.");dz.dropDisabled(false);options.callbacks.processingDroppedFilesComplete(files)}function traverseFileTree(entry){var dirReader,i,parseEntryPromise=new qq.Promise;if(entry.isFile){entry.file(function(file){droppedFiles.push(file);parseEntryPromise.success()},function(fileError){options.callbacks.dropLog("Problem parsing '"+entry.fullPath+"'. FileError code "+fileError.code+".","error");parseEntryPromise.failure()})}else if(entry.isDirectory){dirReader=entry.createReader();dirReader.readEntries(function(entries){var entriesLeft=entries.length;for(i=0;i1&&!options.allowMultipleItems){options.callbacks.processingDroppedFilesComplete([]);options.callbacks.dropError("tooManyFilesError","");dz.dropDisabled(false);handleDataTransferPromise.failure()}else{droppedFiles=[];if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file",unsupportedBrowser:"Unrecoverable error - this browser does not permit file uploading of any kind."},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false,prependFiles:false},paste:{promptForName:false,namePromptMessage:"Please name this image"},showMessage:function(message){setTimeout(function(){window.alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=window.confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)},showPrompt:function(message,defaultValue){var promise=new qq.Promise,retVal=window.prompt(message,defaultValue);if(retVal!=null&&qq.trimStr(retVal).length>0){promise.success(retVal)}else{promise.failure("Undefined or invalid user-supplied value.")}return promise}},true);qq.extend(this._options,o,true);if(!qq.supportedFeatures.uploading||this._options.cors.expected&&!qq.supportedFeatures.uploadCors){this._options.element.innerHTML=""+this._options.messages.unsupportedBrowser+"
"}else{this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop();if(this._options.paste.targetElement&&this._options.paste.promptForName){this._setupPastePrompt()}this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0}};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop();this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dropZoneElements=this._options.dragAndDrop.extraDropzones,preventSelectFiles;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){dropZoneElements.push(this._find(this._options.element,"drop"))}return new qq.DragAndDrop({dropZoneElements:dropZoneElements,hideDropZonesBeforeEnter:this._options.dragAndDrop.hideDropzones,allowMultipleItems:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{processingDroppedFiles:function(){var input=self._button.getInput();qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)},processingDroppedFilesComplete:function(files){var input=self._button.getInput();qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles);if(files){self.addFiles(files)}},dropError:function(code,errorData){self._itemError(code,errorData)},dropLog:function(message,level){self.log(message,level)}}})},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){var item=this.getItemByFileId(id);if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}else{qq(item).addClass(this._classes.retryable);return false}},_onSubmitDelete:function(id){var onSuccessCallback=qq.bind(this._onSubmitDeleteSuccess,this,id);qq.FineUploaderBasic.prototype._onSubmitDelete.call(this,id,onSuccessCallback)},_onSubmitDeleteSuccess:function(id){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}if(this._options.display.prependFiles){this._prependItem(item)}else{this._listElement.appendChild(item)}this._filesInBatchAddedToUi+=1;if(this._options.display.fileSizeOnSubmit&&qq.supportedFeatures.ajaxUploading){this._displayFileSize(id)}},_prependItem:function(item){var parentEl=this._listElement,beforeEl=parentEl.firstChild;if(this._totalFilesInBatch>1&&this._filesInBatchAddedToUi>0){beforeEl=qq(parentEl).children()[this._filesInBatchAddedToUi-1].nextSibling}parentEl.insertBefore(item,beforeEl)},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=item.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)
+}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_itemError:function(code,name){var message=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(message)},_batchError:function(message){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments);this._options.showMessage(message)},_setupPastePrompt:function(){var self=this;this._options.callbacks.onPasteReceived=function(){var message=self._options.paste.namePromptMessage,defaultVal=self._options.paste.defaultName;return self._options.showPrompt(message,defaultVal)}},_fileOrBlobRejected:function(id,name){this._totalFilesInBatch-=1;qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this,arguments)},_prepareItemsForUpload:function(items,params,endpoint){this._totalFilesInBatch=items.length;this._filesInBatchAddedToUi=0;qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this,arguments)}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,handlerImpl,api;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){},onUuidChanged:function(id,newUuid){}};qq.extend(options,o);log=options.log;function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){var cancelRetVal=handlerImpl.cancel(id);if(qq.isPromise(cancelRetVal)){cancelRetVal.then(function(){cancelSuccess(id)})}else if(cancelRetVal!==false){cancelSuccess(id)}},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},reset:function(){log("Resetting upload handler");api.cancelAll();queue=[];handlerImpl.reset()},expunge:function(id){return handlerImpl.expunge(id)},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}};return api};qq.UploadHandlerForm=function(o,uploadCompleteCallback,onUuidChange,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},formHandlerInstanceId=qq.getUniqueId(),api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var iframeName=iframe.id,fileId=getFileIdForIframeName(iframeName);onloadCallbacks[uuids[fileId]]=callback;detachLoadEvents[fileId]=qq(iframe).attach("load",function(){if(inputs[fileId]){log("Received iframe load event for CORS upload request (iframe name "+iframeName+")");postMessageCallbackTimers[iframeName]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for iframe name "+iframeName;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(iframeName,function(message){log("Received the following window message: '"+message+"'");var response=parseResponse(getFileIdForIframeName(iframeName),message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){log("Handling response for iframe name "+iframeName);clearTimeout(postMessageCallbackTimers[iframeName]);delete postMessageCallbackTimers[iframeName];detachLoadEvent(iframeName);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(iframeName);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(id,iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHtml=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHtml);if(innerHtml&&innerHtml.match(/^ ');iframe.setAttribute("id",iframeName);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}function expungeFile(id){delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(getIframeName(id));if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}}function getFileIdForIframeName(iframeName){return iframeName.split("_")[0]}function getIframeName(fileId){return fileId+"_"+formHandlerInstanceId}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){if(api.isValid(id)){return inputs[id].value.replace(/.*(\/|\\)/,"")}else{log(id+" is not a valid item ID.","error")}},isValid:function(id){return inputs[id]!==undefined},reset:function(){inputs=[];uuids=[];detachLoadEvents={};formHandlerInstanceId=qq.getUniqueId()},expunge:function(id){return expungeFile(id)},getUuid:function(id){return uuids[id]},cancel:function(id){var onCancelRetVal=options.onCancel(id,api.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeFile(id)})}else if(onCancelRetVal!==false){expungeFile(id);return true}return false},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,api.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(id,iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove()}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,onUuidChange,logCallback){"use strict";var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.supportedFeatures.chunking,resumeEnabled=options.resume.enabled&&chunkFiles&&qq.supportedFeatures.resume,resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);if(fileState[id]){delete fileState[id].xhr}uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(id,xhr){var response;try{response=qq.parseJson(xhr.responseText);if(response.newUuid!==undefined){log("Server requested UUID change from '"+fileState[id].uuid+"' to '"+response.newUuid+"'");fileState[id].uuid=response.newUuid;onUuidChanged(id,response.newUuid)}}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(id,xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex){var currentChunkIndex;for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}uploadNextChunk(id)}function onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex);calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}function handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex){var name=api.getName(id),firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part),onResumeRetVal;onResumeRetVal=options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume));if(qq.isPromise(onResumeRetVal)){log("Waiting for onResume promise to be fulfilled for "+id);onResumeRetVal.then(function(){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)},function(){log("onResume promise fulfilled - failure indicated. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)})}else if(onResumeRetVal!==false){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)}else{log("onResume callback returned false. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}function handleFileChunkingUpload(id,retry){var firstChunkIndex=0,persistedChunkInfoForResume;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex)}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{uploadNextChunk(id)}}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}function expungeItem(id){var xhr=fileState[id].xhr;if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]}api={add:function(fileOrBlobData){var id,persistedChunkData,uuid=qq.getUniqueId();if(qq.isFile(fileOrBlobData)){id=fileState.push({file:fileOrBlobData})-1}else if(qq.isBlob(fileOrBlobData.blob)){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}if(resumeEnabled){persistedChunkData=getPersistedChunkData(id);if(persistedChunkData){uuid=persistedChunkData.uuid}}fileState[id].uuid=uuid;return id},getName:function(id){if(api.isValid(id)){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}}else{log(id+" is not a valid item ID.","error")}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},expunge:function(id){return expungeItem(id)},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);if(this.isValid(id)){options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}}},cancel:function(id){var onCancelRetVal=options.onCancel(id,this.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeItem(id)})}else if(onCancelRetVal!==false){expungeItem(id);return true}return false},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};!function($){"use strict";var uploader,$el,init,dataStore,pluginOption,pluginOptions,addCallbacks,transformVariables,isValidCommand,delegateCommand;pluginOptions=["uploaderType"];init=function(options){if(options){var xformedOpts=transformVariables(options);addCallbacks(xformedOpts);if(pluginOption("uploaderType")==="basic"){uploader(new qq.FineUploaderBasic(xformedOpts))}else{uploader(new qq.FineUploader(xformedOpts))}}return $el};dataStore=function(key,val){var data=$el.data("fineuploader");if(val){if(data===undefined){data={}}data[key]=val;$el.data("fineuploader",data)}else{if(data===undefined){return null}return data[key]}};uploader=function(instanceToStore){return dataStore("uploader",instanceToStore)};pluginOption=function(option,optionVal){return dataStore(option,optionVal)};addCallbacks=function(transformedOpts){var callbacks=transformedOpts.callbacks={},uploaderInst=new qq.FineUploaderBasic;$.each(uploaderInst._options.callbacks,function(prop,func){var name,$callbackEl;name=/^on(\w+)/.exec(prop)[1];name=name.substring(0,1).toLowerCase()+name.substring(1);$callbackEl=$el;callbacks[prop]=function(){var args=Array.prototype.slice.call(arguments);return $callbackEl.triggerHandler(name,args)}})};transformVariables=function(source,dest){var xformed,arrayVals;if(dest===undefined){if(source.uploaderType!=="basic"){xformed={element:$el[0]}}else{xformed={}}}else{xformed=dest}$.each(source,function(prop,val){if($.inArray(prop,pluginOptions)>=0){pluginOption(prop,val)}else if(val instanceof $){xformed[prop]=val[0]}else if($.isPlainObject(val)){xformed[prop]={};transformVariables(val,xformed[prop])}else if($.isArray(val)){arrayVals=[];$.each(val,function(idx,arrayVal){if(arrayVal instanceof $){$.merge(arrayVals,arrayVal)}else{arrayVals.push(arrayVal)}});xformed[prop]=arrayVals}else{xformed[prop]=val}});if(dest===undefined){return xformed}};isValidCommand=function(command){return $.type(command)==="string"&&!command.match(/^_/)&&uploader()[command]!==undefined};delegateCommand=function(command){var xformedArgs=[],origArgs=Array.prototype.slice.call(arguments,1);transformVariables(origArgs,xformedArgs);return uploader()[command].apply(uploader(),xformedArgs)};$.fn.fineUploader=function(optionsOrCommand){var self=this,selfArgs=arguments,retVals=[];this.each(function(index,el){$el=$(el);if(uploader()&&isValidCommand(optionsOrCommand)){retVals.push(delegateCommand.apply(self,selfArgs));if(self.length===1){return false}}else if(typeof optionsOrCommand==="object"||!optionsOrCommand){init.apply(self,selfArgs)
+}else{$.error("Method "+optionsOrCommand+" does not exist on jQuery.fineUploader")}});if(retVals.length===1){return retVals[0]}else if(retVals.length>1){return retVals}return this}}(jQuery);!function($){"use strict";var rootDataKey="fineUploaderDnd",$el;function init(options){if(!options){options={}}options.dropZoneElements=[$el];var xformedOpts=transformVariables(options);addCallbacks(xformedOpts);dnd(new qq.DragAndDrop(xformedOpts));return $el}function dataStore(key,val){var data=$el.data(rootDataKey);if(val){if(data===undefined){data={}}data[key]=val;$el.data(rootDataKey,data)}else{if(data===undefined){return null}return data[key]}}function dnd(instanceToStore){return dataStore("dndInstance",instanceToStore)}function addCallbacks(transformedOpts){var callbacks=transformedOpts.callbacks={},dndInst=new qq.FineUploaderBasic;$.each(new qq.DragAndDrop.callbacks,function(prop,func){var name=prop,$callbackEl;$callbackEl=$el;callbacks[prop]=function(){var args=Array.prototype.slice.call(arguments),jqueryHandlerResult=$callbackEl.triggerHandler(name,args);return jqueryHandlerResult}})}function transformVariables(source,dest){var xformed,arrayVals;if(dest===undefined){xformed={}}else{xformed=dest}$.each(source,function(prop,val){if(val instanceof $){xformed[prop]=val[0]}else if($.isPlainObject(val)){xformed[prop]={};transformVariables(val,xformed[prop])}else if($.isArray(val)){arrayVals=[];$.each(val,function(idx,arrayVal){if(arrayVal instanceof $){$.merge(arrayVals,arrayVal)}else{arrayVals.push(arrayVal)}});xformed[prop]=arrayVals}else{xformed[prop]=val}});if(dest===undefined){return xformed}}function isValidCommand(command){return $.type(command)==="string"&&command==="dispose"&&dnd()[command]!==undefined}function delegateCommand(command){var xformedArgs=[],origArgs=Array.prototype.slice.call(arguments,1);transformVariables(origArgs,xformedArgs);return dnd()[command].apply(dnd(),xformedArgs)}$.fn.fineUploaderDnd=function(optionsOrCommand){var self=this,selfArgs=arguments,retVals=[];this.each(function(index,el){$el=$(el);if(dnd()&&isValidCommand(optionsOrCommand)){retVals.push(delegateCommand.apply(self,selfArgs));if(self.length===1){return false}}else if(typeof optionsOrCommand==="object"||!optionsOrCommand){init.apply(self,selfArgs)}else{$.error("Method "+optionsOrCommand+" does not exist in Fine Uploader's DnD module.")}});if(retVals.length===1){return retVals[0]}else if(retVals.length>1){return retVals}return this}}(jQuery);
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.3/fineuploader.css b/ajax/libs/file-uploader/3.6.3/fineuploader.css
new file mode 100644
index 000000000..11a436a06
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.3/fineuploader.css
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2013, Widen Enterprises info@fineuploader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+.qq-uploader {
+ position: relative;
+ width: 100%;
+}
+.qq-upload-button {
+ display: block;
+ width: 105px;
+ padding: 7px 0;
+ text-align: center;
+ background: #880000;
+ border-bottom: 1px solid #DDD;
+ color: #FFF;
+}
+.qq-upload-button-hover {
+ background: #CC0000;
+}
+.qq-upload-button-focus {
+ outline: 1px dotted #000000;
+}
+.qq-upload-drop-area, .qq-upload-extra-drop-area {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ min-height: 30px;
+ z-index: 2;
+ background: #FF9797;
+ text-align: center;
+}
+.qq-upload-drop-area span {
+ display: block;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ margin-top: -8px;
+ font-size: 16px;
+}
+.qq-upload-extra-drop-area {
+ position: relative;
+ margin-top: 50px;
+ font-size: 16px;
+ padding-top: 30px;
+ height: 20px;
+ min-height: 40px;
+}
+.qq-upload-drop-area-active {
+ background: #FF7171;
+}
+.qq-upload-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+.qq-upload-list li {
+ margin: 0;
+ padding: 9px;
+ line-height: 15px;
+ font-size: 16px;
+ background-color: #FFF0BD;
+}
+.qq-upload-file, .qq-upload-spinner, .qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text, .qq-upload-finished, .qq-upload-delete {
+ margin-right: 12px;
+}
+.qq-upload-file {
+}
+.qq-upload-spinner {
+ display: inline-block;
+ background: url("loading.gif");
+ width: 15px;
+ height: 15px;
+ vertical-align: text-bottom;
+}
+.qq-drop-processing {
+ display: none;
+}
+.qq-drop-processing-spinner {
+ display: inline-block;
+ background: url("processing.gif");
+ width: 24px;
+ height: 24px;
+ vertical-align: text-bottom;
+}
+.qq-upload-finished {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-retry, .qq-upload-delete {
+ display: none;
+ color: #000000;
+}
+.qq-upload-cancel, .qq-upload-delete {
+ color: #000000;
+}
+.qq-upload-retryable .qq-upload-retry {
+ display: inline;
+}
+.qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-delete {
+ font-size: 12px;
+ font-weight: normal;
+}
+.qq-upload-failed-text {
+ display: none;
+ font-style: italic;
+ font-weight: bold;
+}
+.qq-upload-failed-icon {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-fail .qq-upload-failed-text {
+ display: inline;
+}
+.qq-upload-retrying .qq-upload-failed-text {
+ display: inline;
+ color: #D60000;
+}
+.qq-upload-list li.qq-upload-success {
+ background-color: #5DA30C;
+ color: #FFFFFF;
+}
+.qq-upload-list li.qq-upload-fail {
+ background-color: #D60000;
+ color: #FFFFFF;
+}
+.qq-progress-bar {
+ background: -moz-linear-gradient(top, rgba(30,87,153,1) 0%, rgba(41,137,216,1) 50%, rgba(32,124,202,1) 51%, rgba(125,185,232,1) 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(30,87,153,1)), color-stop(50%,rgba(41,137,216,1)), color-stop(51%,rgba(32,124,202,1)), color-stop(100%,rgba(125,185,232,1))); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* IE10+ */
+ background: linear-gradient(to bottom, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* W3C */
+ width: 0%;
+ height: 15px;
+ border-radius: 6px;
+ margin-bottom: 3px;
+ display: none;
+}
diff --git a/ajax/libs/file-uploader/3.6.3/fineuploader.js b/ajax/libs/file-uploader/3.6.3/fineuploader.js
new file mode 100644
index 000000000..d60fe6c1a
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.3/fineuploader.js
@@ -0,0 +1,4724 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: 3.6.3
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity != null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable && !variable.nodeType && Object.prototype.toString.call(variable) === '[object Object]';
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isArray = function(variable) {
+ "use strict";
+ return Object.prototype.toString.call(variable) === "[object Array]";
+}
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFile = function(maybeFile) {
+ "use strict";
+
+ return window.File && Object.prototype.toString.call(maybeFile) === '[object File]'
+};
+
+qq.isFileList = function(maybeFileList) {
+ return window.FileList && Object.prototype.toString.call(maybeFileList) === '[object FileList]'
+}
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+
+ return qq.isFile(maybeFileOrInput) || qq.isInput(maybeFileOrInput);
+};
+
+qq.isInput = function(maybeInput) {
+ if (window.HTMLInputElement) {
+ if (Object.prototype.toString.call(maybeInput) === '[object HTMLInputElement]') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ if (maybeInput.tagName) {
+ if (maybeInput.tagName.toLowerCase() === 'input') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && Object.prototype.toString.call(maybeBlob) === '[object Blob]';
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice !== undefined || File.prototype.webkitSlice !== undefined || File.prototype.mozSlice !== undefined);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+
+ return first;
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+qq.ios = function() {
+ "use strict";
+ return navigator.userAgent.indexOf("iPad") !== -1
+ || navigator.userAgent.indexOf("iPod") !== -1
+ || navigator.userAgent.indexOf("iPhone") !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(objOrArray, callback) {
+ "use strict";
+ var keyOrIndex, retVal;
+ if (objOrArray) {
+ if (qq.isArray(objOrArray)) {
+ for (keyOrIndex = 0; keyOrIndex < objOrArray.length; keyOrIndex++) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ else {
+ for (keyOrIndex in objOrArray) {
+ if (Object.prototype.hasOwnProperty.call(objOrArray, keyOrIndex)) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+ }
+};
+
+//include any args that should be passed to the new function after the context arg
+qq.bind = function(oldFunc, context) {
+ if (qq.isFunction(oldFunc)) {
+ var args = Array.prototype.slice.call(arguments, 2);
+
+ return function() {
+ if (arguments.length) {
+ args = args.concat(Array.prototype.slice.call(arguments))
+ }
+ return oldFunc.apply(context, args);
+ };
+ }
+
+ throw new Error("first parameter must be a function!");
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ cookie;
+
+ qq.each(ca, function(idx, part) {
+ var cookiePart = part;
+ while (cookiePart.charAt(0)==' ') {
+ cookiePart = cookiePart.substring(1, cookiePart.length);
+ }
+
+ if (cookiePart.indexOf(nameEQ) === 0) {
+ cookie = cookiePart.substring(nameEQ.length, cookiePart.length);
+ return false;
+ }
+ });
+
+ return cookie;
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+qq.version="-unstable-";qq.supportedFeatures = (function() {
+ var supportsUploading,
+ supportsAjaxFileUploading,
+ supportsFolderDrop,
+ supportsChunking,
+ supportsResume,
+ supportsUploadViaPaste,
+ supportsUploadCors,
+ supportsDeleteFileCors;
+
+
+ function testSupportsFileInputElement() {
+ var supported = true,
+ tempInput;
+
+ try {
+ tempInput = document.createElement('input');
+ tempInput.type = 'file';
+ qq(tempInput).hide();
+
+ if(tempInput.disabled) {
+ supported = false;
+ }
+ }
+ catch(ex) {
+ supported = false;
+ }
+
+ return supported;
+ }
+
+ //only way to test for Filesystem API support since webkit does not expose the DataTransfer interface
+ function isChrome21OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[2][1-9]|Chrome\/[3-9][0-9]/) !== undefined;
+ }
+
+ //only way to test for complete Clipboard API support at this time
+ function isChrome14OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/) !== undefined;
+ }
+
+
+ supportsUploading = testSupportsFileInputElement();
+
+ supportsAjaxFileUploading = supportsUploading && qq.isXhrUploadSupported();
+
+ supportsFolderDrop = supportsAjaxFileUploading && isChrome21OrHigher();
+
+ supportsChunking = supportsAjaxFileUploading && qq.isFileChunkingSupported();
+
+ supportsResume = supportsAjaxFileUploading && supportsChunking && qq.areCookiesEnabled();
+
+ supportsUploadViaPaste = supportsAjaxFileUploading && isChrome14OrHigher();
+
+ supportsUploadCors = supportsUploading && (window.postMessage !== undefined || supportsAjaxFileUploading);
+
+ supportsDeleteFileCors = supportsAjaxFileUploading;
+
+
+ return {
+ uploading: supportsUploading,
+ ajaxUploading: supportsAjaxFileUploading,
+ fileDrop: supportsAjaxFileUploading, //NOTE: will also return true for touch-only devices. It's not currently possible to accurately test for touch-only devices
+ folderDrop: supportsFolderDrop,
+ chunking: supportsChunking,
+ resume: supportsResume,
+ uploadCustomHeaders: supportsAjaxFileUploading,
+ uploadNonMultipart: supportsAjaxFileUploading,
+ itemSizeValidation: supportsAjaxFileUploading,
+ uploadViaPaste: supportsUploadViaPaste,
+ progressBar: supportsAjaxFileUploading,
+ uploadCors: supportsUploadCors,
+ deleteFileCors: supportsDeleteFileCors,
+ canDetermineSize: supportsAjaxFileUploading
+ }
+
+}());
+/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallbacks = [],
+ failureCallbacks = [],
+ doneCallbacks = [],
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ if (onSuccess) {
+ successCallbacks.push(onSuccess);
+ }
+ if (onFailure) {
+ failureCallbacks.push(onFailure);
+ }
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+
+ return this;
+ },
+
+ done: function(callback) {
+ if (state === 0) {
+ doneCallbacks.push(callback);
+ }
+ else {
+ callback();
+ }
+
+ return this;
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallbacks.length) {
+ qq.each(successCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallbacks.length) {
+ qq.each(failureCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ }
+ };
+};
+
+qq.isPromise = function(maybePromise) {
+ return maybePromise && maybePromise.then && maybePromise.done;
+};/*globals qq*/
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ disposeSupport = new qq.DisposeSupport(),
+ options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input) {},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+
+ qq.extend(options, o);
+
+ // make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};qq.UploadData = function(uploaderProxy) {
+ var data = [],
+ byId = {},
+ byUuid = {},
+ byStatus = {},
+ api;
+
+ function getDataByIds(ids) {
+ if (qq.isArray(ids)) {
+ var entries = [];
+
+ qq.each(ids, function(idx, id) {
+ entries.push(data[byId[id]]);
+ });
+
+ return entries;
+ }
+
+ return data[byId[ids]];
+ }
+
+ function getDataByUuids(uuids) {
+ if (qq.isArray(uuids)) {
+ var entries = [];
+
+ qq.each(uuids, function(idx, uuid) {
+ entries.push(data[byUuid[uuid]]);
+ });
+
+ return entries;
+ }
+
+ return data[byUuid[uuids]];
+ }
+
+ function getDataByStatus(status) {
+ var statusResults = [],
+ statuses = [].concat(status);
+
+ qq.each(statuses, function(index, statusEnum) {
+ var statusResultIndexes = byStatus[statusEnum];
+
+ if (statusResultIndexes !== undefined) {
+ qq.each(statusResultIndexes, function(i, dataIndex) {
+ statusResults.push(data[dataIndex]);
+ });
+ }
+ });
+
+ return statusResults;
+ }
+
+ api = {
+ added: function(id) {
+ var uuid = uploaderProxy.getUuid(id),
+ name = uploaderProxy.getName(id),
+ size = uploaderProxy.getSize(id),
+ status = qq.status.SUBMITTING;
+
+ var index = data.push({
+ id: id,
+ name: name,
+ uuid: uuid,
+ size: size,
+ status: status
+ }) - 1;
+
+ byId[id] = index;
+
+ byUuid[uuid] = index;
+
+ if (byStatus[status] === undefined) {
+ byStatus[status] = [];
+ }
+ byStatus[status].push(index);
+
+ uploaderProxy.onStatusChange(id, undefined, status);
+ },
+
+ retrieve: function(optionalFilter) {
+ if (qq.isObject(optionalFilter) && data.length) {
+ if (optionalFilter.id !== undefined) {
+ return getDataByIds(optionalFilter.id);
+ }
+
+ else if (optionalFilter.uuid !== undefined) {
+ return getDataByUuids(optionalFilter.uuid);
+ }
+
+ else if (optionalFilter.status) {
+ return getDataByStatus(optionalFilter.status);
+ }
+ }
+ else {
+ return qq.extend([], data, true);
+ }
+ },
+
+ reset: function() {
+ data = [];
+ byId = {};
+ byUuid = {};
+ byStatus = {};
+ },
+
+ setStatus: function(id, newStatus) {
+ var dataIndex = byId[id],
+ oldStatus = data[dataIndex].status,
+ byStatusOldStatusIndex = qq.indexOf(byStatus[oldStatus], dataIndex);
+
+ byStatus[oldStatus].splice(byStatusOldStatusIndex, 1);
+
+ data[dataIndex].status = newStatus;
+
+ if (byStatus[newStatus] === undefined) {
+ byStatus[newStatus] = [];
+ }
+ byStatus[newStatus].push(dataIndex);
+
+ uploaderProxy.onStatusChange(id, oldStatus, newStatus);
+ },
+
+ uuidChanged: function(id, newUuid) {
+ var dataIndex = byId[id],
+ oldUuid = data[dataIndex].uuid;
+
+ data[dataIndex].uuid = newUuid;
+ byUuid[newUuid] = dataIndex;
+ delete byUuid[oldUuid];
+ }
+ };
+
+ return api;
+};
+
+qq.status = {
+ SUBMITTING: "submitting",
+ SUBMITTED: "submitted",
+ REJECTED: "rejected",
+ QUEUED: "queued",
+ CANCELED: "canceled",
+ UPLOADING: "uploading",
+ UPLOAD_RETRYING: "retrying upload",
+ UPLOAD_SUCCESSFUL: "upload successful",
+ UPLOAD_FAILED: "upload failed",
+ DELETE_FAILED: "delete failed",
+ DELETING: "deleting",
+ DELETED: "deleted"
+};qq.FineUploaderBasic = function(o) {
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true,
+ acceptFiles: null
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON, maybeXhr){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){},
+ onPasteReceived: function(blob) {},
+ onStatusChange: function(id, oldStatus, newStatus) {}
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ defaultResponseError: "Upload failure reason unknown",
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'misc_data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ },
+ camera: {
+ ios: false
+ }
+ };
+
+ qq.extend(this._options, o, true);
+
+ this._handleCameraAccess();
+
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData = this._createUploadDataTracker();
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader ' + qq.version + '] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader ' + qq.version + '] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function() {
+ return this._filesInProgress.length;
+ },
+ getNetUploads: function() {
+ return this._netUploaded;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netUploadedOrQueued++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData.reset();
+
+ if (this._pasteHandler) {
+ this._pasteHandler.reset();
+ }
+ },
+ addFiles: function(filesOrInputs, params, endpoint) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ fileOrInputIndex, fileOrInput, fileIndex;
+
+ if (filesOrInputs) {
+ if (!qq.isFileList(filesOrInputs)) {
+ filesOrInputs = [].concat(filesOrInputs);
+ }
+
+ for (fileOrInputIndex = 0; fileOrInputIndex < filesOrInputs.length; fileOrInputIndex+=1) {
+ fileOrInput = filesOrInputs[fileOrInputIndex];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ if (qq.isInput(fileOrInput) && qq.supportedFeatures.ajaxUploading) {
+ for (fileIndex = 0; fileIndex < fileOrInput.files.length; fileIndex++) {
+ verifiedFilesOrInputs.push(fileOrInput.files[fileIndex]);
+ }
+ }
+ else {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Received ' + verifiedFilesOrInputs.length + ' files or inputs.');
+ this._prepareItemsForUpload(verifiedFilesOrInputs, params, endpoint);
+ }
+ },
+ addBlobs: function(blobDataOrArray, params, endpoint) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._prepareItemsForUpload(verifiedBlobDataList, params, endpoint);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ doesExist: function(fileOrBlobId) {
+ return this._handler.isValid(fileOrBlobId);
+ },
+ getUploads: function(optionalFilter) {
+ return this._uploadData.retrieve(optionalFilter);
+ },
+ _handleCheckedCallback: function(details) {
+ var self = this,
+ callbackRetVal = details.callback();
+
+ if (qq.isPromise(callbackRetVal)) {
+ this.log(details.name + " - waiting for " + details.name + " promise to be fulfilled for " + details.identifier);
+ return callbackRetVal.then(
+ function(successParam) {
+ self.log(details.name + " promise success for " + details.identifier);
+ details.onSuccess(successParam);
+ },
+ function() {
+ if (details.onFailure) {
+ self.log(details.name + " promise failure for " + details.identifier);
+ details.onFailure();
+ }
+ else {
+ self.log(details.name + " promise failure for " + details.identifier);
+ }
+ });
+ }
+
+ if (callbackRetVal !== false) {
+ details.onSuccess(callbackRetVal);
+ }
+ else {
+ if (details.onFailure) {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Invoking failure callback.")
+ details.onFailure();
+ }
+ else {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Will not proceed.")
+ }
+ }
+
+ return callbackRetVal;
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.supportedFeatures.ajaxUploading,
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result, xhr);
+ },
+ onCancel: function(id, name) {
+ return self._handleCheckedCallback({
+ name: "onCancel",
+ callback: qq.bind(self._options.callbacks.onCancel, self, id, name),
+ onSuccess: qq.bind(self._onCancel, self, id, name),
+ identifier: id
+ });
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ onUuidChanged: function(id, newUuid) {
+ self._uploadData.uuidChanged(id, newUuid);
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ self._handleCheckedCallback({
+ name: "onPasteReceived",
+ callback: qq.bind(self._options.callbacks.onPasteReceived, self, blob),
+ onSuccess: qq.bind(self._handlePasteSuccess, self, blob),
+ identifier: "pasted image"
+ });
+ }
+ }
+ });
+ },
+ _createUploadDataTracker: function() {
+ var self = this;
+
+ return new qq.UploadData({
+ getName: function(id) {
+ return self.getName(id);
+ },
+ getUuid: function(id) {
+ return self.getUuid(id);
+ },
+ getSize: function(id) {
+ return self.getSize(id);
+ },
+ onStatusChange: function(id, oldStatus, newStatus) {
+ self._options.callbacks.onStatusChange(id, oldStatus, newStatus);
+ }
+ });
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total) {
+ //nothing to do yet in core uploader
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_FAILED);
+ this._netUploadedOrQueued--;
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_SUCCESSFUL);
+ this._netUploaded++;
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.CANCELED);
+
+ this._netUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected || qq.supportedFeatures.deleteFileCors));
+ },
+ _onSubmitDelete: function(id, onSuccessCallback) {
+ if (this._isDeletePossible()) {
+ return this._handleCheckedCallback({
+ name: "onSubmitDelete",
+ callback: qq.bind(this._options.callbacks.onSubmitDelete, this, id),
+ onSuccess: onSuccessCallback || qq.bind(this._deleteHandler.sendDelete, this, id, this.getUuid(id)),
+ identifier: id
+ });
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(id) {
+ this._uploadData.setStatus(id, qq.status.DELETING);
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this._uploadData.setStatus(id, qq.status.DELETE_FAILED);
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.DELETED);
+ this._netUploadedOrQueued--;
+ this._netUploaded--;
+ this._handler.expunge(id);
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.UPLOADING);
+ },
+ _onInputChange: function(input){
+ if (qq.supportedFeatures.ajaxUploading) {
+ this.addFiles(input.files);
+ }
+ else {
+ this.addFiles(input);
+ }
+
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems", "");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : this._options.text.defaultResponseError;
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ var validationDescriptors = this._getValidationDescriptors(items);
+
+ this._handleCheckedCallback({
+ name: "onValidateBatch",
+ callback: qq.bind(this._options.callbacks.onValidateBatch, this, validationDescriptors),
+ onSuccess: qq.bind(this._onValidateBatchCallbackSuccess, this, validationDescriptors, items, params, endpoint),
+ identifier: "batch validation"
+ });
+ },
+ _upload: function(blobOrFileContainer, params, endpoint) {
+ var id = this._handler.add(blobOrFileContainer),
+ name = this._handler.getName(id);
+
+ this._uploadData.added(id);
+
+ if (params) {
+ this.setParams(params, id);
+ }
+
+ if (endpoint) {
+ this.setEndpoint(endpoint, id);
+ }
+
+ this._handleCheckedCallback({
+ name: "onSubmit",
+ callback: qq.bind(this._options.callbacks.onSubmit, this, id, name),
+ onSuccess: qq.bind(this._onSubmitCallbackSuccess, this, id, name),
+ onFailure: qq.bind(this._fileOrBlobRejected, this, id, name),
+ identifier: id
+ });
+ },
+ _onSubmitCallbackSuccess: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.SUBMITTED);
+
+ this._onSubmit(id, name);
+ this._options.callbacks.onSubmitted(id, name);
+
+ if (this._options.autoUpload) {
+ if (!this._handler.upload(id)) {
+ this._uploadData.setStatus(id, qq.status.QUEUED);
+ }
+ }
+ else {
+ this._storeForLater(id);
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _onValidateBatchCallbackSuccess: function(validationDescriptors, items, params, endpoint) {
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netUploadedOrQueued + validationDescriptors.length;
+
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ if (items.length > 0) {
+ this._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(this._options.callbacks.onValidate, this, items[0]),
+ onSuccess: qq.bind(this._onValidateCallbackSuccess, this, items, 0, params, endpoint),
+ onFailure: qq.bind(this._onValidateCallbackFailure, this, items, 0, params, endpoint),
+ identifier: "Item '" + items[0].name + "', size: " + items[0].size
+ });
+ }
+ else {
+ this._itemError("noFilesError", "");
+ }
+ }
+ else {
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ },
+ _onValidateCallbackSuccess: function(items, index, params, endpoint) {
+ var nextIndex = index+1,
+ validationDescriptor = this._getValidationDescriptor(items[index]),
+ validItem = false;
+
+ if (this._validateFileOrBlobData(items[index], validationDescriptor)) {
+ validItem = true;
+ this._upload(items[index], params, endpoint);
+ }
+
+ this._maybeProcessNextItemAfterOnValidateCallback(validItem, items, nextIndex, params, endpoint);
+ },
+ _onValidateCallbackFailure: function(items, index, params, endpoint) {
+ var nextIndex = index+ 1;
+
+ this._fileOrBlobRejected(undefined, items[0].name);
+
+ this._maybeProcessNextItemAfterOnValidateCallback(false, items, nextIndex, params, endpoint);
+ },
+ _maybeProcessNextItemAfterOnValidateCallback: function(validItem, items, index, params, endpoint) {
+ var self = this;
+
+ if (items.length > index) {
+ if (validItem || !this._options.validation.stopOnFirstInvalidFile) {
+ //use setTimeout to prevent a stack overflow with a large number of files in the batch & non-promissory callbacks
+ setTimeout(function() {
+ var validationDescriptor = self._getValidationDescriptor(items[index]);
+
+ self._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(self._options.callbacks.onValidate, self, items[index]),
+ onSuccess: qq.bind(self._onValidateCallbackSuccess, self, items, index, params, endpoint),
+ onFailure: qq.bind(self._onValidateCallbackFailure, self, items, index, params, endpoint),
+ identifier: "Item '" + validationDescriptor.name + "', size: " + validationDescriptor.size
+ });
+ }, 0);
+ }
+ }
+ },
+ _validateFileOrBlobData: function(item, validationDescriptor) {
+ var name = validationDescriptor.name,
+ size = validationDescriptor.size,
+ valid = true;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ valid = false;
+ }
+
+ if (qq.isFileOrInput(item) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ valid = false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ valid = false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ valid = false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ valid = false;
+ }
+
+ if (!valid) {
+ this._fileOrBlobRejected(undefined, name);
+ }
+
+ return valid;
+ },
+ _fileOrBlobRejected: function(id, name) {
+ if (id !== undefined) {
+ this._uploadData.setStatus(id, qq.status.REJECTED);
+ }
+ },
+ _itemError: function(code, nameOrNames) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ names = [].concat(nameOrNames),
+ name = names[0],
+ extensionsForMessage, placeholderMatch;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ placeholderMatch = message.match(/(\{\w+\})/g);
+ if (placeholderMatch !== null) {
+ qq.each(placeholderMatch, function(idx, placeholder) {
+ r(placeholder, names[idx]);
+ });
+ }
+
+ this._options.callbacks.onError(null, name, message, undefined);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message, undefined);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1000;
+ i++;
+ } while (bytes > 999);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size !== undefined) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ },
+ _handleCameraAccess: function() {
+ if (this._options.camera.ios && qq.ios()) {
+ this._options.multiple = false;
+
+ if (this._options.validation.acceptFiles === null) {
+ this._options.validation.acceptFiles = "image/*;capture=camera";
+ }
+ else {
+ this._options.validation.acceptFiles += ",image/*;capture=camera";
+ }
+ }
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz,
+ droppedFiles = [],
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropZoneElements: [],
+ hideDropZonesBeforeEnter: false,
+ allowMultipleItems: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: new qq.DragAndDrop.callbacks()
+ };
+
+ qq.extend(options, o, true);
+
+ setupDragDrop();
+
+ function uploadDroppedFiles(files) {
+ options.callbacks.dropLog('Grabbed ' + files.length + " dropped files.");
+ dz.dropDisabled(false);
+ options.callbacks.processingDroppedFilesComplete(files);
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i,
+ parseEntryPromise = new qq.Promise();
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ droppedFiles.push(file);
+ parseEntryPromise.success();
+ },
+ function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+ else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ var entriesLeft = entries.length;
+
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]).done(function() {
+ entriesLeft-=1;
+
+ if (entriesLeft === 0) {
+ parseEntryPromise.success();
+ }
+ });
+ }
+
+ if (!entries.length) {
+ parseEntryPromise.success();
+ }
+ }, function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+
+ return parseEntryPromise;
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry,
+ pendingFolderPromises = [],
+ handleDataTransferPromise = new qq.Promise();
+
+ options.callbacks.processingDroppedFiles();
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.allowMultipleItems) {
+ options.callbacks.processingDroppedFilesComplete([]);
+ options.callbacks.dropError('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ handleDataTransferPromise.failure();
+ }
+ else {
+ droppedFiles = [];
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ }
+
+ else {
+ pendingFolderPromises.push(traverseFileTree(entry).done(function() {
+ pendingFolderPromises.pop();
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }));
+ }
+ }
+ }
+ }
+ else {
+ droppedFiles = dataTransfer.files;
+ }
+
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }
+
+ return handleDataTransferPromise;
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer).done(function() {
+ uploadDroppedFiles(droppedFiles);
+ });
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ var dropZones = options.dropZoneElements;
+
+ qq.each(dropZones, function(idx, dropZone) {
+ setupDropzone(dropZone);
+ })
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (dropZones.length && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).css({display: 'block'});
+ });
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropZonesBeforeEnter && qq.FineUploader.prototype._leaving_document_out(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setupExtraDropzone: function(element) {
+ options.dropZoneElements.push(element);
+ setupDropzone(element);
+ },
+
+ removeDropzone: function(element) {
+ var i,
+ dzs = options.dropZoneElements;
+
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+qq.DragAndDrop.callbacks = function() {
+ return {
+ processingDroppedFiles: function() {},
+ processingDroppedFilesComplete: function(files) {},
+ dropError: function(code, errorSpecifics) {
+ qq.log("Drag & drop error code '" + code + " with these specifics: '" + errorSpecifics + "'", "error");
+ },
+ dropLog: function(message, level) {
+ qq.log(message, level);
+ }
+ }
+}
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file",
+ unsupportedBrowser: "Unrecoverable error - this browser does not permit file uploading of any kind."
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false,
+ prependFiles: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+
+ if (!qq.supportedFeatures.uploading || (this._options.cors.expected && !qq.supportedFeatures.uploadCors)) {
+ this._options.element.innerHTML = "" + this._options.messages.unsupportedBrowser + "
"
+ }
+ else {
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dropZoneElements = this._options.dragAndDrop.extraDropzones,
+ preventSelectFiles;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ dropZoneElements.push(this._find(this._options.element, 'drop'));
+ }
+
+ return new qq.DragAndDrop({
+ dropZoneElements: dropZoneElements,
+ hideDropZonesBeforeEnter: this._options.dragAndDrop.hideDropzones,
+ allowMultipleItems: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ processingDroppedFiles: function() {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ },
+ processingDroppedFilesComplete: function(files) {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ dropError: function(code, errorData) {
+ self._itemError(code, errorData);
+ },
+ dropLog: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type) {
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name) {
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ var onSuccessCallback = qq.bind(this._onSubmitDeleteSuccess, this, id);
+
+ qq.FineUploaderBasic.prototype._onSubmitDelete.call(this, id, onSuccessCallback);
+ },
+ _onSubmitDeleteSuccess: function(id) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ if (this._options.display.prependFiles) {
+ this._prependItem(item);
+ }
+ else {
+ this._listElement.appendChild(item);
+ }
+ this._filesInBatchAddedToUi += 1;
+
+ if (this._options.display.fileSizeOnSubmit && qq.supportedFeatures.ajaxUploading) {
+ this._displayFileSize(id);
+ }
+ },
+ _prependItem: function(item) {
+ var parentEl = this._listElement,
+ beforeEl = parentEl.firstChild;
+
+ if (this._totalFilesInBatch > 1 && this._filesInBatchAddedToUi > 0) {
+ beforeEl = qq(parentEl).children()[this._filesInBatchAddedToUi - 1].nextSibling;
+
+ }
+
+ parentEl.insertBefore(item, beforeEl);
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = item.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ },
+ _fileOrBlobRejected: function(id, name) {
+ this._totalFilesInBatch -= 1;
+ qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this, arguments);
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ this._totalFilesInBatch = items.length;
+ this._filesInBatchAddedToUi = 0;
+ qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this, arguments);
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, handlerImpl, api;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){},
+ onUuidChanged: function(id, newUuid){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.supportedFeatures.ajaxUploading) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, options.onUuidChanged, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, options.onUuidChanged, log);
+ }
+
+ function cancelSuccess(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ dequeue(id);
+ }
+
+
+ api = {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ handlerImpl.upload(id);
+ return true;
+ }
+
+ return false;
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ var cancelRetVal = handlerImpl.cancel(id);
+
+ if (qq.isPromise(cancelRetVal)) {
+ cancelRetVal.then(function() {
+ cancelSuccess(id);
+ });
+ }
+ else if (cancelRetVal !== false) {
+ cancelSuccess(id);
+ }
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ api.cancelAll();
+ queue = [];
+ handlerImpl.reset();
+ },
+ expunge: function(id) {
+ return handlerImpl.expunge(id);
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, onUuidChange, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ formHandlerInstanceId = qq.getUniqueId(),
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var iframeName = iframe.id,
+ fileId = getFileIdForIframeName(iframeName);
+
+ onloadCallbacks[uuids[fileId]] = callback;
+
+ detachLoadEvents[fileId] = qq(iframe).attach('load', function() {
+ if (inputs[fileId]) {
+ log("Received iframe load event for CORS upload request (iframe name " + iframeName + ")");
+
+ postMessageCallbackTimers[iframeName] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for iframe name " + iframeName;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(iframeName, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = parseResponse(getFileIdForIframeName(iframeName), message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ log("Handling response for iframe name " + iframeName);
+ clearTimeout(postMessageCallbackTimers[iframeName]);
+ delete postMessageCallbackTimers[iframeName];
+
+ detachLoadEvent(iframeName);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(iframeName);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(id, iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHtml = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHtml);
+ //plain text response may be wrapped in tag
+ if (innerHtml && innerHtml.match(/^ ');
+
+ iframe.setAttribute('id', iframeName);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+ function expungeFile(id) {
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(getIframeName(id));
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ }
+
+ function getFileIdForIframeName(iframeName) {
+ return iframeName.split("_")[0];
+ }
+
+ function getIframeName(fileId) {
+ return fileId + "_" + formHandlerInstanceId;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ formHandlerInstanceId = qq.getUniqueId();
+ },
+ expunge: function(id) {
+ return expungeFile(id);
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, api.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeFile(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeFile(id);
+ return true;
+ }
+
+ return false;
+ },
+
+ upload: function(id) {
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, api.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(id, iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, onUuidChange, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.supportedFeatures.chunking,
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.supportedFeatures.resume,
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+ options.onComplete(id, name, response, xhr);
+
+ if (fileState[id]) {
+ delete fileState[id].xhr;
+ }
+
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(id, xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+
+ if (response.newUuid !== undefined) {
+ log("Server requested UUID change from '" + fileState[id].uuid + "' to '" + response.newUuid + "'");
+ fileState[id].uuid = response.newUuid;
+ onUuidChanged(id, response.newUuid);
+ }
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(id, xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex) {
+ var currentChunkIndex;
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+
+ function handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex) {
+ var name = api.getName(id),
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part),
+ onResumeRetVal;
+
+ onResumeRetVal = options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume));
+ if (qq.isPromise(onResumeRetVal)) {
+ log("Waiting for onResume promise to be fulfilled for " + id);
+ onResumeRetVal.then(
+ function() {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ },
+ function() {
+ log("onResume promise fulfilled - failure indicated. Will not resume.")
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ );
+ }
+ else if (onResumeRetVal !== false) {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ }
+ else {
+ log("onResume callback returned false. Will not resume.");
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var firstChunkIndex = 0,
+ persistedChunkInfoForResume;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex);
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ uploadNextChunk(id);
+ }
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+ function expungeItem(id) {
+ var xhr = fileState[id].xhr;
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ }
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id, persistedChunkData,
+ uuid = qq.getUniqueId();
+
+ if (qq.isFile(fileOrBlobData)) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (qq.isBlob(fileOrBlobData.blob)) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ if (resumeEnabled) {
+ persistedChunkData = getPersistedChunkData(id);
+
+ if (persistedChunkData) {
+ uuid = persistedChunkData.uuid;
+ }
+ }
+
+ fileState[id].uuid = uuid;
+
+ return id;
+ },
+ getName: function(id){
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ expunge: function(id) {
+ return expungeItem(id);
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry) {
+ var name = this.getName(id);
+
+ if (this.isValid(id)) {
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ }
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, this.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeItem(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeItem(id);
+ return true;
+ }
+
+ return false;
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
diff --git a/ajax/libs/file-uploader/3.6.3/fineuploader.min.css b/ajax/libs/file-uploader/3.6.3/fineuploader.min.css
new file mode 100644
index 000000000..110e8fa52
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.3/fineuploader.min.css
@@ -0,0 +1 @@
+.qq-uploader{position:relative;width:100%;}.qq-upload-button{display:block;width:105px;padding:7px 0;text-align:center;background:#800;border-bottom:1px solid #DDD;color:#FFF;}.qq-upload-button-hover{background:#C00;}.qq-upload-button-focus{outline:1px dotted #000;}.qq-upload-drop-area,.qq-upload-extra-drop-area{position:absolute;top:0;left:0;width:100%;height:100%;min-height:30px;z-index:2;background:#FF9797;text-align:center;}.qq-upload-drop-area span{display:block;position:absolute;top:50%;width:100%;margin-top:-8px;font-size:16px;}.qq-upload-extra-drop-area{position:relative;margin-top:50px;font-size:16px;padding-top:30px;height:20px;min-height:40px;}.qq-upload-drop-area-active{background:#FF7171;}.qq-upload-list{margin:0;padding:0;list-style:none;}.qq-upload-list li{margin:0;padding:9px;line-height:15px;font-size:16px;background-color:#FFF0BD;}.qq-upload-file,.qq-upload-spinner,.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-failed-text,.qq-upload-finished,.qq-upload-delete{margin-right:12px;}.qq-upload-spinner{display:inline-block;background:url("loading.gif");width:15px;height:15px;vertical-align:text-bottom;}.qq-drop-processing{display:none;}.qq-drop-processing-spinner{display:inline-block;background:url("processing.gif");width:24px;height:24px;vertical-align:text-bottom;}.qq-upload-finished{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-retry,.qq-upload-delete{display:none;color:#000;}.qq-upload-cancel,.qq-upload-delete{color:#000;}.qq-upload-retryable .qq-upload-retry{display:inline;}.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-delete{font-size:12px;font-weight:normal;}.qq-upload-failed-text{display:none;font-style:italic;font-weight:bold;}.qq-upload-failed-icon{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-fail .qq-upload-failed-text{display:inline;}.qq-upload-retrying .qq-upload-failed-text{display:inline;color:#D60000;}.qq-upload-list li.qq-upload-success{background-color:#5DA30C;color:#FFF;}.qq-upload-list li.qq-upload-fail{background-color:#D60000;color:#FFF;}.qq-progress-bar{background:-moz-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,rgba(30,87,153,1)),color-stop(50%,rgba(41,137,216,1)),color-stop(51%,rgba(32,124,202,1)),color-stop(100%,rgba(125,185,232,1)));background:-webkit-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-o-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-ms-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:linear-gradient(to bottom,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);width:0;height:15px;border-radius:6px;margin-bottom:3px;display:none;}
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.3/fineuploader.min.js b/ajax/libs/file-uploader/3.6.3/fineuploader.min.js
new file mode 100644
index 000000000..8c289675f
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.3/fineuploader.min.js
@@ -0,0 +1,15 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: 3.6.3
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!=null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable&&!variable.nodeType&&Object.prototype.toString.call(variable)==="[object Object]"};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isArray=function(variable){"use strict";return Object.prototype.toString.call(variable)==="[object Array]"};qq.isString=function(maybeString){"use strict";return Object.prototype.toString.call(maybeString)==="[object String]"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFile=function(maybeFile){"use strict";return window.File&&Object.prototype.toString.call(maybeFile)==="[object File]"};qq.isFileList=function(maybeFileList){return window.FileList&&Object.prototype.toString.call(maybeFileList)==="[object FileList]"};qq.isFileOrInput=function(maybeFileOrInput){"use strict";return qq.isFile(maybeFileOrInput)||qq.isInput(maybeFileOrInput)};qq.isInput=function(maybeInput){if(window.HTMLInputElement){if(Object.prototype.toString.call(maybeInput)==="[object HTMLInputElement]"){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}if(maybeInput.tagName){if(maybeInput.tagName.toLowerCase()==="input"){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&Object.prototype.toString.call(maybeBlob)==="[object Blob]"};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice!==undefined||File.prototype.webkitSlice!==undefined||File.prototype.mozSlice!==undefined)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}});return first};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{defaultResponseError:"Upload failure reason unknown",sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"misc_data",paramNames:{name:"qqblobname"}},paste:{targetElement:null,defaultName:"pasted_image"},camera:{ios:false}};qq.extend(this._options,o,true);this._handleCameraAccess();this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData=this._createUploadDataTracker();this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}if(this._options.paste.targetElement){this._pasteHandler=this._createPasteHandler()}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader "+qq.version+"] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader "+qq.version+"] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},getNetUploads:function(){return this._netUploaded},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._netUploadedOrQueued++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset();this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData.reset();if(this._pasteHandler){this._pasteHandler.reset()}},addFiles:function(filesOrInputs,params,endpoint){var self=this,verifiedFilesOrInputs=[],fileOrInputIndex,fileOrInput,fileIndex;if(filesOrInputs){if(!qq.isFileList(filesOrInputs)){filesOrInputs=[].concat(filesOrInputs)}for(fileOrInputIndex=0;fileOrInputIndex=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||qq.supportedFeatures.deleteFileCors)},_onSubmitDelete:function(id,onSuccessCallback){if(this._isDeletePossible()){return this._handleCheckedCallback({name:"onSubmitDelete",callback:qq.bind(this._options.callbacks.onSubmitDelete,this,id),onSuccess:onSuccessCallback||qq.bind(this._deleteHandler.sendDelete,this,id,this.getUuid(id)),identifier:id})}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(id){this._uploadData.setStatus(id,qq.status.DELETING)},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);
+if(isError){this._uploadData.setStatus(id,qq.status.DELETE_FAILED);this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status,xhr)}else{this._uploadData.setStatus(id,qq.status.DELETED);this._netUploadedOrQueued--;this._netUploaded--;this._handler.expunge(id);this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){this._uploadData.setStatus(id,qq.status.UPLOADING)},_onInputChange:function(input){if(qq.supportedFeatures.ajaxUploading){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0&&this._netUploadedOrQueued+1>itemLimit){this._itemError("retryFailTooManyItems","");return false}this.log("Retrying upload for '"+fileName+"' (id: "+id+")...");this._filesInProgress.push(id);return true}else{this.log("'"+id+"' is not a valid file ID","error");return false}},_maybeParseAndSendUploadError:function(id,name,response,xhr){if(!response.success){if(xhr&&xhr.status!==200&&!response.error){this._options.callbacks.onError(id,name,"XHR returned response code "+xhr.status,xhr)}else{var errorReason=response.error?response.error:this._options.text.defaultResponseError;this._options.callbacks.onError(id,name,errorReason,xhr)}}},_prepareItemsForUpload:function(items,params,endpoint){var validationDescriptors=this._getValidationDescriptors(items);this._handleCheckedCallback({name:"onValidateBatch",callback:qq.bind(this._options.callbacks.onValidateBatch,this,validationDescriptors),onSuccess:qq.bind(this._onValidateBatchCallbackSuccess,this,validationDescriptors,items,params,endpoint),identifier:"batch validation"})},_upload:function(blobOrFileContainer,params,endpoint){var id=this._handler.add(blobOrFileContainer),name=this._handler.getName(id);this._uploadData.added(id);if(params){this.setParams(params,id)}if(endpoint){this.setEndpoint(endpoint,id)}this._handleCheckedCallback({name:"onSubmit",callback:qq.bind(this._options.callbacks.onSubmit,this,id,name),onSuccess:qq.bind(this._onSubmitCallbackSuccess,this,id,name),onFailure:qq.bind(this._fileOrBlobRejected,this,id,name),identifier:id})},_onSubmitCallbackSuccess:function(id,name){this._uploadData.setStatus(id,qq.status.SUBMITTED);this._onSubmit(id,name);this._options.callbacks.onSubmitted(id,name);if(this._options.autoUpload){if(!this._handler.upload(id)){this._uploadData.setStatus(id,qq.status.QUEUED)}}else{this._storeForLater(id)}},_storeForLater:function(id){this._storedIds.push(id)},_onValidateBatchCallbackSuccess:function(validationDescriptors,items,params,endpoint){var errorMessage,itemLimit=this._options.validation.itemLimit,proposedNetFilesUploadedOrQueued=this._netUploadedOrQueued+validationDescriptors.length;if(itemLimit===0||proposedNetFilesUploadedOrQueued<=itemLimit){if(items.length>0){this._handleCheckedCallback({name:"onValidate",callback:qq.bind(this._options.callbacks.onValidate,this,items[0]),onSuccess:qq.bind(this._onValidateCallbackSuccess,this,items,0,params,endpoint),onFailure:qq.bind(this._onValidateCallbackFailure,this,items,0,params,endpoint),identifier:"Item '"+items[0].name+"', size: "+items[0].size})}else{this._itemError("noFilesError","")}}else{errorMessage=this._options.messages.tooManyItemsError.replace(/\{netItems\}/g,proposedNetFilesUploadedOrQueued).replace(/\{itemLimit\}/g,itemLimit);this._batchError(errorMessage)}},_onValidateCallbackSuccess:function(items,index,params,endpoint){var nextIndex=index+1,validationDescriptor=this._getValidationDescriptor(items[index]),validItem=false;if(this._validateFileOrBlobData(items[index],validationDescriptor)){validItem=true;this._upload(items[index],params,endpoint)}this._maybeProcessNextItemAfterOnValidateCallback(validItem,items,nextIndex,params,endpoint)},_onValidateCallbackFailure:function(items,index,params,endpoint){var nextIndex=index+1;this._fileOrBlobRejected(undefined,items[0].name);this._maybeProcessNextItemAfterOnValidateCallback(false,items,nextIndex,params,endpoint)},_maybeProcessNextItemAfterOnValidateCallback:function(validItem,items,index,params,endpoint){var self=this;if(items.length>index){if(validItem||!this._options.validation.stopOnFirstInvalidFile){setTimeout(function(){var validationDescriptor=self._getValidationDescriptor(items[index]);self._handleCheckedCallback({name:"onValidate",callback:qq.bind(self._options.callbacks.onValidate,self,items[index]),onSuccess:qq.bind(self._onValidateCallbackSuccess,self,items,index,params,endpoint),onFailure:qq.bind(self._onValidateCallbackFailure,self,items,index,params,endpoint),identifier:"Item '"+validationDescriptor.name+"', size: "+validationDescriptor.size})},0)}}},_validateFileOrBlobData:function(item,validationDescriptor){var name=validationDescriptor.name,size=validationDescriptor.size,valid=true;if(this._options.callbacks.onValidate(validationDescriptor)===false){valid=false}if(qq.isFileOrInput(item)&&!this._isAllowedExtension(name)){this._itemError("typeError",name);valid=false}else if(size===0){this._itemError("emptyError",name);valid=false}else if(size&&this._options.validation.sizeLimit&&size>this._options.validation.sizeLimit){this._itemError("sizeError",name);valid=false}else if(size&&size999);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size!==undefined){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}},_handleCameraAccess:function(){if(this._options.camera.ios&&qq.ios()){this._options.multiple=false;if(this._options.validation.acceptFiles===null){this._options.validation.acceptFiles="image/*;capture=camera"}else{this._options.validation.acceptFiles+=",image/*;capture=camera"}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,droppedFiles=[],disposeSupport=new qq.DisposeSupport;options={dropZoneElements:[],hideDropZonesBeforeEnter:false,allowMultipleItems:true,classes:{dropActive:null},callbacks:new qq.DragAndDrop.callbacks};qq.extend(options,o,true);setupDragDrop();function uploadDroppedFiles(files){options.callbacks.dropLog("Grabbed "+files.length+" dropped files.");dz.dropDisabled(false);options.callbacks.processingDroppedFilesComplete(files)}function traverseFileTree(entry){var dirReader,i,parseEntryPromise=new qq.Promise;if(entry.isFile){entry.file(function(file){droppedFiles.push(file);parseEntryPromise.success()},function(fileError){options.callbacks.dropLog("Problem parsing '"+entry.fullPath+"'. FileError code "+fileError.code+".","error");parseEntryPromise.failure()})}else if(entry.isDirectory){dirReader=entry.createReader();dirReader.readEntries(function(entries){var entriesLeft=entries.length;for(i=0;i1&&!options.allowMultipleItems){options.callbacks.processingDroppedFilesComplete([]);options.callbacks.dropError("tooManyFilesError","");dz.dropDisabled(false);handleDataTransferPromise.failure()}else{droppedFiles=[];if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file",unsupportedBrowser:"Unrecoverable error - this browser does not permit file uploading of any kind."},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false,prependFiles:false},paste:{promptForName:false,namePromptMessage:"Please name this image"},showMessage:function(message){setTimeout(function(){window.alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=window.confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)},showPrompt:function(message,defaultValue){var promise=new qq.Promise,retVal=window.prompt(message,defaultValue);if(retVal!=null&&qq.trimStr(retVal).length>0){promise.success(retVal)}else{promise.failure("Undefined or invalid user-supplied value.")}return promise}},true);qq.extend(this._options,o,true);if(!qq.supportedFeatures.uploading||this._options.cors.expected&&!qq.supportedFeatures.uploadCors){this._options.element.innerHTML=""+this._options.messages.unsupportedBrowser+"
"}else{this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop();if(this._options.paste.targetElement&&this._options.paste.promptForName){this._setupPastePrompt()}this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0}};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop();this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dropZoneElements=this._options.dragAndDrop.extraDropzones,preventSelectFiles;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){dropZoneElements.push(this._find(this._options.element,"drop"))}return new qq.DragAndDrop({dropZoneElements:dropZoneElements,hideDropZonesBeforeEnter:this._options.dragAndDrop.hideDropzones,allowMultipleItems:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{processingDroppedFiles:function(){var input=self._button.getInput();qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)},processingDroppedFilesComplete:function(files){var input=self._button.getInput();qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles);if(files){self.addFiles(files)}},dropError:function(code,errorData){self._itemError(code,errorData)},dropLog:function(message,level){self.log(message,level)}}})},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){var item=this.getItemByFileId(id);if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}else{qq(item).addClass(this._classes.retryable);return false}},_onSubmitDelete:function(id){var onSuccessCallback=qq.bind(this._onSubmitDeleteSuccess,this,id);qq.FineUploaderBasic.prototype._onSubmitDelete.call(this,id,onSuccessCallback)},_onSubmitDeleteSuccess:function(id){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}if(this._options.display.prependFiles){this._prependItem(item)}else{this._listElement.appendChild(item)}this._filesInBatchAddedToUi+=1;if(this._options.display.fileSizeOnSubmit&&qq.supportedFeatures.ajaxUploading){this._displayFileSize(id)}},_prependItem:function(item){var parentEl=this._listElement,beforeEl=parentEl.firstChild;if(this._totalFilesInBatch>1&&this._filesInBatchAddedToUi>0){beforeEl=qq(parentEl).children()[this._filesInBatchAddedToUi-1].nextSibling}parentEl.insertBefore(item,beforeEl)},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=item.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)
+}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_itemError:function(code,name){var message=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(message)},_batchError:function(message){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments);this._options.showMessage(message)},_setupPastePrompt:function(){var self=this;this._options.callbacks.onPasteReceived=function(){var message=self._options.paste.namePromptMessage,defaultVal=self._options.paste.defaultName;return self._options.showPrompt(message,defaultVal)}},_fileOrBlobRejected:function(id,name){this._totalFilesInBatch-=1;qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this,arguments)},_prepareItemsForUpload:function(items,params,endpoint){this._totalFilesInBatch=items.length;this._filesInBatchAddedToUi=0;qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this,arguments)}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,handlerImpl,api;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){},onUuidChanged:function(id,newUuid){}};qq.extend(options,o);log=options.log;function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){var cancelRetVal=handlerImpl.cancel(id);if(qq.isPromise(cancelRetVal)){cancelRetVal.then(function(){cancelSuccess(id)})}else if(cancelRetVal!==false){cancelSuccess(id)}},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},reset:function(){log("Resetting upload handler");api.cancelAll();queue=[];handlerImpl.reset()},expunge:function(id){return handlerImpl.expunge(id)},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}};return api};qq.UploadHandlerForm=function(o,uploadCompleteCallback,onUuidChange,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},formHandlerInstanceId=qq.getUniqueId(),api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var iframeName=iframe.id,fileId=getFileIdForIframeName(iframeName);onloadCallbacks[uuids[fileId]]=callback;detachLoadEvents[fileId]=qq(iframe).attach("load",function(){if(inputs[fileId]){log("Received iframe load event for CORS upload request (iframe name "+iframeName+")");postMessageCallbackTimers[iframeName]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for iframe name "+iframeName;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(iframeName,function(message){log("Received the following window message: '"+message+"'");var response=parseResponse(getFileIdForIframeName(iframeName),message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){log("Handling response for iframe name "+iframeName);clearTimeout(postMessageCallbackTimers[iframeName]);delete postMessageCallbackTimers[iframeName];detachLoadEvent(iframeName);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(iframeName);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(id,iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHtml=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHtml);if(innerHtml&&innerHtml.match(/^ ');iframe.setAttribute("id",iframeName);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}function expungeFile(id){delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(getIframeName(id));if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}}function getFileIdForIframeName(iframeName){return iframeName.split("_")[0]}function getIframeName(fileId){return fileId+"_"+formHandlerInstanceId}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){if(api.isValid(id)){return inputs[id].value.replace(/.*(\/|\\)/,"")}else{log(id+" is not a valid item ID.","error")}},isValid:function(id){return inputs[id]!==undefined},reset:function(){inputs=[];uuids=[];detachLoadEvents={};formHandlerInstanceId=qq.getUniqueId()},expunge:function(id){return expungeFile(id)},getUuid:function(id){return uuids[id]},cancel:function(id){var onCancelRetVal=options.onCancel(id,api.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeFile(id)})}else if(onCancelRetVal!==false){expungeFile(id);return true}return false},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,api.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(id,iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove()}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,onUuidChange,logCallback){"use strict";var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.supportedFeatures.chunking,resumeEnabled=options.resume.enabled&&chunkFiles&&qq.supportedFeatures.resume,resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);if(fileState[id]){delete fileState[id].xhr}uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(id,xhr){var response;try{response=qq.parseJson(xhr.responseText);if(response.newUuid!==undefined){log("Server requested UUID change from '"+fileState[id].uuid+"' to '"+response.newUuid+"'");fileState[id].uuid=response.newUuid;onUuidChanged(id,response.newUuid)}}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(id,xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex){var currentChunkIndex;for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}uploadNextChunk(id)}function onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex);calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}function handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex){var name=api.getName(id),firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part),onResumeRetVal;onResumeRetVal=options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume));if(qq.isPromise(onResumeRetVal)){log("Waiting for onResume promise to be fulfilled for "+id);onResumeRetVal.then(function(){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)},function(){log("onResume promise fulfilled - failure indicated. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)})}else if(onResumeRetVal!==false){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)}else{log("onResume callback returned false. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}function handleFileChunkingUpload(id,retry){var firstChunkIndex=0,persistedChunkInfoForResume;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex)}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{uploadNextChunk(id)}}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}function expungeItem(id){var xhr=fileState[id].xhr;if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]}api={add:function(fileOrBlobData){var id,persistedChunkData,uuid=qq.getUniqueId();if(qq.isFile(fileOrBlobData)){id=fileState.push({file:fileOrBlobData})-1}else if(qq.isBlob(fileOrBlobData.blob)){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}if(resumeEnabled){persistedChunkData=getPersistedChunkData(id);if(persistedChunkData){uuid=persistedChunkData.uuid}}fileState[id].uuid=uuid;return id},getName:function(id){if(api.isValid(id)){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}}else{log(id+" is not a valid item ID.","error")}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},expunge:function(id){return expungeItem(id)},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);if(this.isValid(id)){options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}}},cancel:function(id){var onCancelRetVal=options.onCancel(id,this.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeItem(id)})}else if(onCancelRetVal!==false){expungeItem(id);return true}return false},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.3/iframe.xss.response.js b/ajax/libs/file-uploader/3.6.3/iframe.xss.response.js
new file mode 100644
index 000000000..e11fca1e5
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.3/iframe.xss.response.js
@@ -0,0 +1,6 @@
+(function() {
+ var match = /(\{.+\}).+/.exec(document.body.innerHTML);
+ if (match) {
+ parent.postMessage(match[1], '*');
+ }
+}());
diff --git a/ajax/libs/file-uploader/3.6.3/iframe.xss.response.min.js b/ajax/libs/file-uploader/3.6.3/iframe.xss.response.min.js
new file mode 100644
index 000000000..345cf3603
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.3/iframe.xss.response.min.js
@@ -0,0 +1 @@
+!function(){var match=/(\{.+\}).+/.exec(document.body.innerHTML);if(match){parent.postMessage(match[1],"*")}}();
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.3/loading.gif b/ajax/libs/file-uploader/3.6.3/loading.gif
new file mode 100644
index 000000000..6fba77609
Binary files /dev/null and b/ajax/libs/file-uploader/3.6.3/loading.gif differ
diff --git a/ajax/libs/file-uploader/3.6.3/processing.gif b/ajax/libs/file-uploader/3.6.3/processing.gif
new file mode 100644
index 000000000..7c99504e1
Binary files /dev/null and b/ajax/libs/file-uploader/3.6.3/processing.gif differ
diff --git a/ajax/libs/file-uploader/3.6.4/fineuploader-jquery.js b/ajax/libs/file-uploader/3.6.4/fineuploader-jquery.js
new file mode 100644
index 000000000..a914091f2
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.4/fineuploader-jquery.js
@@ -0,0 +1,5039 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: -unstable-
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity != null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable && !variable.nodeType && Object.prototype.toString.call(variable) === '[object Object]';
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isArray = function(variable) {
+ "use strict";
+ return Object.prototype.toString.call(variable) === "[object Array]";
+}
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFile = function(maybeFile) {
+ "use strict";
+
+ return window.File && Object.prototype.toString.call(maybeFile) === '[object File]'
+};
+
+qq.isFileList = function(maybeFileList) {
+ return window.FileList && Object.prototype.toString.call(maybeFileList) === '[object FileList]'
+}
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+
+ return qq.isFile(maybeFileOrInput) || qq.isInput(maybeFileOrInput);
+};
+
+qq.isInput = function(maybeInput) {
+ if (window.HTMLInputElement) {
+ if (Object.prototype.toString.call(maybeInput) === '[object HTMLInputElement]') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ if (maybeInput.tagName) {
+ if (maybeInput.tagName.toLowerCase() === 'input') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && Object.prototype.toString.call(maybeBlob) === '[object Blob]';
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice !== undefined || File.prototype.webkitSlice !== undefined || File.prototype.mozSlice !== undefined);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+
+ return first;
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+qq.ios = function() {
+ "use strict";
+ return navigator.userAgent.indexOf("iPad") !== -1
+ || navigator.userAgent.indexOf("iPod") !== -1
+ || navigator.userAgent.indexOf("iPhone") !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(objOrArray, callback) {
+ "use strict";
+ var keyOrIndex, retVal;
+ if (objOrArray) {
+ if (qq.isArray(objOrArray)) {
+ for (keyOrIndex = 0; keyOrIndex < objOrArray.length; keyOrIndex++) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ else {
+ for (keyOrIndex in objOrArray) {
+ if (Object.prototype.hasOwnProperty.call(objOrArray, keyOrIndex)) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+ }
+};
+
+//include any args that should be passed to the new function after the context arg
+qq.bind = function(oldFunc, context) {
+ if (qq.isFunction(oldFunc)) {
+ var args = Array.prototype.slice.call(arguments, 2);
+
+ return function() {
+ if (arguments.length) {
+ args = args.concat(Array.prototype.slice.call(arguments))
+ }
+ return oldFunc.apply(context, args);
+ };
+ }
+
+ throw new Error("first parameter must be a function!");
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ cookie;
+
+ qq.each(ca, function(idx, part) {
+ var cookiePart = part;
+ while (cookiePart.charAt(0)==' ') {
+ cookiePart = cookiePart.substring(1, cookiePart.length);
+ }
+
+ if (cookiePart.indexOf(nameEQ) === 0) {
+ cookie = cookiePart.substring(nameEQ.length, cookiePart.length);
+ return false;
+ }
+ });
+
+ return cookie;
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+qq.version="-unstable-";qq.supportedFeatures = (function() {
+ var supportsUploading,
+ supportsAjaxFileUploading,
+ supportsFolderDrop,
+ supportsChunking,
+ supportsResume,
+ supportsUploadViaPaste,
+ supportsUploadCors,
+ supportsDeleteFileCors;
+
+
+ function testSupportsFileInputElement() {
+ var supported = true,
+ tempInput;
+
+ try {
+ tempInput = document.createElement('input');
+ tempInput.type = 'file';
+ qq(tempInput).hide();
+
+ if(tempInput.disabled) {
+ supported = false;
+ }
+ }
+ catch(ex) {
+ supported = false;
+ }
+
+ return supported;
+ }
+
+ //only way to test for Filesystem API support since webkit does not expose the DataTransfer interface
+ function isChrome21OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[2][1-9]|Chrome\/[3-9][0-9]/) !== undefined;
+ }
+
+ //only way to test for complete Clipboard API support at this time
+ function isChrome14OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/) !== undefined;
+ }
+
+
+ supportsUploading = testSupportsFileInputElement();
+
+ supportsAjaxFileUploading = supportsUploading && qq.isXhrUploadSupported();
+
+ supportsFolderDrop = supportsAjaxFileUploading && isChrome21OrHigher();
+
+ supportsChunking = supportsAjaxFileUploading && qq.isFileChunkingSupported();
+
+ supportsResume = supportsAjaxFileUploading && supportsChunking && qq.areCookiesEnabled();
+
+ supportsUploadViaPaste = supportsAjaxFileUploading && isChrome14OrHigher();
+
+ supportsUploadCors = supportsUploading && (window.postMessage !== undefined || supportsAjaxFileUploading);
+
+ supportsDeleteFileCors = supportsAjaxFileUploading;
+
+
+ return {
+ uploading: supportsUploading,
+ ajaxUploading: supportsAjaxFileUploading,
+ fileDrop: supportsAjaxFileUploading, //NOTE: will also return true for touch-only devices. It's not currently possible to accurately test for touch-only devices
+ folderDrop: supportsFolderDrop,
+ chunking: supportsChunking,
+ resume: supportsResume,
+ uploadCustomHeaders: supportsAjaxFileUploading,
+ uploadNonMultipart: supportsAjaxFileUploading,
+ itemSizeValidation: supportsAjaxFileUploading,
+ uploadViaPaste: supportsUploadViaPaste,
+ progressBar: supportsAjaxFileUploading,
+ uploadCors: supportsUploadCors,
+ deleteFileCors: supportsDeleteFileCors,
+ canDetermineSize: supportsAjaxFileUploading
+ }
+
+}());
+/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallbacks = [],
+ failureCallbacks = [],
+ doneCallbacks = [],
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ if (onSuccess) {
+ successCallbacks.push(onSuccess);
+ }
+ if (onFailure) {
+ failureCallbacks.push(onFailure);
+ }
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+
+ return this;
+ },
+
+ done: function(callback) {
+ if (state === 0) {
+ doneCallbacks.push(callback);
+ }
+ else {
+ callback();
+ }
+
+ return this;
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallbacks.length) {
+ qq.each(successCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallbacks.length) {
+ qq.each(failureCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ }
+ };
+};
+
+qq.isPromise = function(maybePromise) {
+ return maybePromise && maybePromise.then && maybePromise.done;
+};/*globals qq*/
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ disposeSupport = new qq.DisposeSupport(),
+ options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input) {},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+
+ qq.extend(options, o);
+
+ // make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};qq.UploadData = function(uploaderProxy) {
+ var data = [],
+ byId = {},
+ byUuid = {},
+ byStatus = {},
+ api;
+
+ function getDataByIds(ids) {
+ if (qq.isArray(ids)) {
+ var entries = [];
+
+ qq.each(ids, function(idx, id) {
+ entries.push(data[byId[id]]);
+ });
+
+ return entries;
+ }
+
+ return data[byId[ids]];
+ }
+
+ function getDataByUuids(uuids) {
+ if (qq.isArray(uuids)) {
+ var entries = [];
+
+ qq.each(uuids, function(idx, uuid) {
+ entries.push(data[byUuid[uuid]]);
+ });
+
+ return entries;
+ }
+
+ return data[byUuid[uuids]];
+ }
+
+ function getDataByStatus(status) {
+ var statusResults = [],
+ statuses = [].concat(status);
+
+ qq.each(statuses, function(index, statusEnum) {
+ var statusResultIndexes = byStatus[statusEnum];
+
+ if (statusResultIndexes !== undefined) {
+ qq.each(statusResultIndexes, function(i, dataIndex) {
+ statusResults.push(data[dataIndex]);
+ });
+ }
+ });
+
+ return statusResults;
+ }
+
+ api = {
+ added: function(id) {
+ var uuid = uploaderProxy.getUuid(id),
+ name = uploaderProxy.getName(id),
+ size = uploaderProxy.getSize(id),
+ status = qq.status.SUBMITTING;
+
+ var index = data.push({
+ id: id,
+ name: name,
+ uuid: uuid,
+ size: size,
+ status: status
+ }) - 1;
+
+ byId[id] = index;
+
+ byUuid[uuid] = index;
+
+ if (byStatus[status] === undefined) {
+ byStatus[status] = [];
+ }
+ byStatus[status].push(index);
+
+ uploaderProxy.onStatusChange(id, undefined, status);
+ },
+
+ retrieve: function(optionalFilter) {
+ if (qq.isObject(optionalFilter) && data.length) {
+ if (optionalFilter.id !== undefined) {
+ return getDataByIds(optionalFilter.id);
+ }
+
+ else if (optionalFilter.uuid !== undefined) {
+ return getDataByUuids(optionalFilter.uuid);
+ }
+
+ else if (optionalFilter.status) {
+ return getDataByStatus(optionalFilter.status);
+ }
+ }
+ else {
+ return qq.extend([], data, true);
+ }
+ },
+
+ reset: function() {
+ data = [];
+ byId = {};
+ byUuid = {};
+ byStatus = {};
+ },
+
+ setStatus: function(id, newStatus) {
+ var dataIndex = byId[id],
+ oldStatus = data[dataIndex].status,
+ byStatusOldStatusIndex = qq.indexOf(byStatus[oldStatus], dataIndex);
+
+ byStatus[oldStatus].splice(byStatusOldStatusIndex, 1);
+
+ data[dataIndex].status = newStatus;
+
+ if (byStatus[newStatus] === undefined) {
+ byStatus[newStatus] = [];
+ }
+ byStatus[newStatus].push(dataIndex);
+
+ uploaderProxy.onStatusChange(id, oldStatus, newStatus);
+ },
+
+ uuidChanged: function(id, newUuid) {
+ var dataIndex = byId[id],
+ oldUuid = data[dataIndex].uuid;
+
+ data[dataIndex].uuid = newUuid;
+ byUuid[newUuid] = dataIndex;
+ delete byUuid[oldUuid];
+ }
+ };
+
+ return api;
+};
+
+qq.status = {
+ SUBMITTING: "submitting",
+ SUBMITTED: "submitted",
+ REJECTED: "rejected",
+ QUEUED: "queued",
+ CANCELED: "canceled",
+ UPLOADING: "uploading",
+ UPLOAD_RETRYING: "retrying upload",
+ UPLOAD_SUCCESSFUL: "upload successful",
+ UPLOAD_FAILED: "upload failed",
+ DELETE_FAILED: "delete failed",
+ DELETING: "deleting",
+ DELETED: "deleted"
+};qq.FineUploaderBasic = function(o) {
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true,
+ acceptFiles: null
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON, maybeXhr){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){},
+ onPasteReceived: function(blob) {},
+ onStatusChange: function(id, oldStatus, newStatus) {}
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ defaultResponseError: "Upload failure reason unknown",
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'misc_data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ },
+ camera: {
+ ios: false
+ }
+ };
+
+ qq.extend(this._options, o, true);
+
+ this._handleCameraAccess();
+
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData = this._createUploadDataTracker();
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader ' + qq.version + '] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader ' + qq.version + '] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function() {
+ return this._filesInProgress.length;
+ },
+ getNetUploads: function() {
+ return this._netUploaded;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netUploadedOrQueued++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData.reset();
+
+ if (this._pasteHandler) {
+ this._pasteHandler.reset();
+ }
+ },
+ addFiles: function(filesOrInputs, params, endpoint) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ fileOrInputIndex, fileOrInput, fileIndex;
+
+ if (filesOrInputs) {
+ if (!qq.isFileList(filesOrInputs)) {
+ filesOrInputs = [].concat(filesOrInputs);
+ }
+
+ for (fileOrInputIndex = 0; fileOrInputIndex < filesOrInputs.length; fileOrInputIndex+=1) {
+ fileOrInput = filesOrInputs[fileOrInputIndex];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ if (qq.isInput(fileOrInput) && qq.supportedFeatures.ajaxUploading) {
+ for (fileIndex = 0; fileIndex < fileOrInput.files.length; fileIndex++) {
+ verifiedFilesOrInputs.push(fileOrInput.files[fileIndex]);
+ }
+ }
+ else {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Received ' + verifiedFilesOrInputs.length + ' files or inputs.');
+ this._prepareItemsForUpload(verifiedFilesOrInputs, params, endpoint);
+ }
+ },
+ addBlobs: function(blobDataOrArray, params, endpoint) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._prepareItemsForUpload(verifiedBlobDataList, params, endpoint);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ doesExist: function(fileOrBlobId) {
+ return this._handler.isValid(fileOrBlobId);
+ },
+ getUploads: function(optionalFilter) {
+ return this._uploadData.retrieve(optionalFilter);
+ },
+ _handleCheckedCallback: function(details) {
+ var self = this,
+ callbackRetVal = details.callback();
+
+ if (qq.isPromise(callbackRetVal)) {
+ this.log(details.name + " - waiting for " + details.name + " promise to be fulfilled for " + details.identifier);
+ return callbackRetVal.then(
+ function(successParam) {
+ self.log(details.name + " promise success for " + details.identifier);
+ details.onSuccess(successParam);
+ },
+ function() {
+ if (details.onFailure) {
+ self.log(details.name + " promise failure for " + details.identifier);
+ details.onFailure();
+ }
+ else {
+ self.log(details.name + " promise failure for " + details.identifier);
+ }
+ });
+ }
+
+ if (callbackRetVal !== false) {
+ details.onSuccess(callbackRetVal);
+ }
+ else {
+ if (details.onFailure) {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Invoking failure callback.")
+ details.onFailure();
+ }
+ else {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Will not proceed.")
+ }
+ }
+
+ return callbackRetVal;
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.supportedFeatures.ajaxUploading,
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result, xhr);
+ },
+ onCancel: function(id, name) {
+ return self._handleCheckedCallback({
+ name: "onCancel",
+ callback: qq.bind(self._options.callbacks.onCancel, self, id, name),
+ onSuccess: qq.bind(self._onCancel, self, id, name),
+ identifier: id
+ });
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ onUuidChanged: function(id, newUuid) {
+ self._uploadData.uuidChanged(id, newUuid);
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ self._handleCheckedCallback({
+ name: "onPasteReceived",
+ callback: qq.bind(self._options.callbacks.onPasteReceived, self, blob),
+ onSuccess: qq.bind(self._handlePasteSuccess, self, blob),
+ identifier: "pasted image"
+ });
+ }
+ }
+ });
+ },
+ _createUploadDataTracker: function() {
+ var self = this;
+
+ return new qq.UploadData({
+ getName: function(id) {
+ return self.getName(id);
+ },
+ getUuid: function(id) {
+ return self.getUuid(id);
+ },
+ getSize: function(id) {
+ return self.getSize(id);
+ },
+ onStatusChange: function(id, oldStatus, newStatus) {
+ self._options.callbacks.onStatusChange(id, oldStatus, newStatus);
+ }
+ });
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total) {
+ //nothing to do yet in core uploader
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_FAILED);
+ this._netUploadedOrQueued--;
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_SUCCESSFUL);
+ this._netUploaded++;
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.CANCELED);
+
+ this._netUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected || qq.supportedFeatures.deleteFileCors));
+ },
+ _onSubmitDelete: function(id, onSuccessCallback) {
+ if (this._isDeletePossible()) {
+ return this._handleCheckedCallback({
+ name: "onSubmitDelete",
+ callback: qq.bind(this._options.callbacks.onSubmitDelete, this, id),
+ onSuccess: onSuccessCallback || qq.bind(this._deleteHandler.sendDelete, this, id, this.getUuid(id)),
+ identifier: id
+ });
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(id) {
+ this._uploadData.setStatus(id, qq.status.DELETING);
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this._uploadData.setStatus(id, qq.status.DELETE_FAILED);
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.DELETED);
+ this._netUploadedOrQueued--;
+ this._netUploaded--;
+ this._handler.expunge(id);
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.UPLOADING);
+ },
+ _onInputChange: function(input){
+ if (qq.supportedFeatures.ajaxUploading) {
+ this.addFiles(input.files);
+ }
+ else {
+ this.addFiles(input);
+ }
+
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems", "");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : this._options.text.defaultResponseError;
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ var validationDescriptors = this._getValidationDescriptors(items);
+
+ this._handleCheckedCallback({
+ name: "onValidateBatch",
+ callback: qq.bind(this._options.callbacks.onValidateBatch, this, validationDescriptors),
+ onSuccess: qq.bind(this._onValidateBatchCallbackSuccess, this, validationDescriptors, items, params, endpoint),
+ identifier: "batch validation"
+ });
+ },
+ _upload: function(blobOrFileContainer, params, endpoint) {
+ var id = this._handler.add(blobOrFileContainer),
+ name = this._handler.getName(id);
+
+ this._uploadData.added(id);
+
+ if (params) {
+ this.setParams(params, id);
+ }
+
+ if (endpoint) {
+ this.setEndpoint(endpoint, id);
+ }
+
+ this._handleCheckedCallback({
+ name: "onSubmit",
+ callback: qq.bind(this._options.callbacks.onSubmit, this, id, name),
+ onSuccess: qq.bind(this._onSubmitCallbackSuccess, this, id, name),
+ onFailure: qq.bind(this._fileOrBlobRejected, this, id, name),
+ identifier: id
+ });
+ },
+ _onSubmitCallbackSuccess: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.SUBMITTED);
+
+ this._onSubmit(id, name);
+ this._options.callbacks.onSubmitted(id, name);
+
+ if (this._options.autoUpload) {
+ if (!this._handler.upload(id)) {
+ this._uploadData.setStatus(id, qq.status.QUEUED);
+ }
+ }
+ else {
+ this._storeForLater(id);
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _onValidateBatchCallbackSuccess: function(validationDescriptors, items, params, endpoint) {
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netUploadedOrQueued + validationDescriptors.length;
+
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ if (items.length > 0) {
+ this._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(this._options.callbacks.onValidate, this, items[0]),
+ onSuccess: qq.bind(this._onValidateCallbackSuccess, this, items, 0, params, endpoint),
+ onFailure: qq.bind(this._onValidateCallbackFailure, this, items, 0, params, endpoint),
+ identifier: "Item '" + items[0].name + "', size: " + items[0].size
+ });
+ }
+ else {
+ this._itemError("noFilesError", "");
+ }
+ }
+ else {
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ },
+ _onValidateCallbackSuccess: function(items, index, params, endpoint) {
+ var nextIndex = index+1,
+ validationDescriptor = this._getValidationDescriptor(items[index]),
+ validItem = false;
+
+ if (this._validateFileOrBlobData(items[index], validationDescriptor)) {
+ validItem = true;
+ this._upload(items[index], params, endpoint);
+ }
+
+ this._maybeProcessNextItemAfterOnValidateCallback(validItem, items, nextIndex, params, endpoint);
+ },
+ _onValidateCallbackFailure: function(items, index, params, endpoint) {
+ var nextIndex = index+ 1;
+
+ this._fileOrBlobRejected(undefined, items[0].name);
+
+ this._maybeProcessNextItemAfterOnValidateCallback(false, items, nextIndex, params, endpoint);
+ },
+ _maybeProcessNextItemAfterOnValidateCallback: function(validItem, items, index, params, endpoint) {
+ var self = this;
+
+ if (items.length > index) {
+ if (validItem || !this._options.validation.stopOnFirstInvalidFile) {
+ //use setTimeout to prevent a stack overflow with a large number of files in the batch & non-promissory callbacks
+ setTimeout(function() {
+ var validationDescriptor = self._getValidationDescriptor(items[index]);
+
+ self._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(self._options.callbacks.onValidate, self, items[index]),
+ onSuccess: qq.bind(self._onValidateCallbackSuccess, self, items, index, params, endpoint),
+ onFailure: qq.bind(self._onValidateCallbackFailure, self, items, index, params, endpoint),
+ identifier: "Item '" + validationDescriptor.name + "', size: " + validationDescriptor.size
+ });
+ }, 0);
+ }
+ }
+ },
+ _validateFileOrBlobData: function(item, validationDescriptor) {
+ var name = validationDescriptor.name,
+ size = validationDescriptor.size,
+ valid = true;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ valid = false;
+ }
+
+ if (qq.isFileOrInput(item) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ valid = false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ valid = false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ valid = false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ valid = false;
+ }
+
+ if (!valid) {
+ this._fileOrBlobRejected(undefined, name);
+ }
+
+ return valid;
+ },
+ _fileOrBlobRejected: function(id, name) {
+ if (id !== undefined) {
+ this._uploadData.setStatus(id, qq.status.REJECTED);
+ }
+ },
+ _itemError: function(code, nameOrNames) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ names = [].concat(nameOrNames),
+ name = names[0],
+ extensionsForMessage, placeholderMatch;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ placeholderMatch = message.match(/(\{\w+\})/g);
+ if (placeholderMatch !== null) {
+ qq.each(placeholderMatch, function(idx, placeholder) {
+ r(placeholder, names[idx]);
+ });
+ }
+
+ this._options.callbacks.onError(null, name, message, undefined);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message, undefined);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1000;
+ i++;
+ } while (bytes > 999);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size !== undefined) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ },
+ _handleCameraAccess: function() {
+ if (this._options.camera.ios && qq.ios()) {
+ this._options.multiple = false;
+
+ if (this._options.validation.acceptFiles === null) {
+ this._options.validation.acceptFiles = "image/*;capture=camera";
+ }
+ else {
+ this._options.validation.acceptFiles += ",image/*;capture=camera";
+ }
+ }
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz,
+ droppedFiles = [],
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropZoneElements: [],
+ hideDropZonesBeforeEnter: false,
+ allowMultipleItems: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: new qq.DragAndDrop.callbacks()
+ };
+
+ qq.extend(options, o, true);
+
+ setupDragDrop();
+
+ function uploadDroppedFiles(files) {
+ options.callbacks.dropLog('Grabbed ' + files.length + " dropped files.");
+ dz.dropDisabled(false);
+ options.callbacks.processingDroppedFilesComplete(files);
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i,
+ parseEntryPromise = new qq.Promise();
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ droppedFiles.push(file);
+ parseEntryPromise.success();
+ },
+ function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+ else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ var entriesLeft = entries.length;
+
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]).done(function() {
+ entriesLeft-=1;
+
+ if (entriesLeft === 0) {
+ parseEntryPromise.success();
+ }
+ });
+ }
+
+ if (!entries.length) {
+ parseEntryPromise.success();
+ }
+ }, function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+
+ return parseEntryPromise;
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry,
+ pendingFolderPromises = [],
+ handleDataTransferPromise = new qq.Promise();
+
+ options.callbacks.processingDroppedFiles();
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.allowMultipleItems) {
+ options.callbacks.processingDroppedFilesComplete([]);
+ options.callbacks.dropError('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ handleDataTransferPromise.failure();
+ }
+ else {
+ droppedFiles = [];
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ }
+
+ else {
+ pendingFolderPromises.push(traverseFileTree(entry).done(function() {
+ pendingFolderPromises.pop();
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }));
+ }
+ }
+ }
+ }
+ else {
+ droppedFiles = dataTransfer.files;
+ }
+
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }
+
+ return handleDataTransferPromise;
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer).done(function() {
+ uploadDroppedFiles(droppedFiles);
+ });
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ var dropZones = options.dropZoneElements;
+
+ qq.each(dropZones, function(idx, dropZone) {
+ setupDropzone(dropZone);
+ })
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (dropZones.length && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).css({display: 'block'});
+ });
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropZonesBeforeEnter && qq.FineUploader.prototype._leaving_document_out(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setupExtraDropzone: function(element) {
+ options.dropZoneElements.push(element);
+ setupDropzone(element);
+ },
+
+ removeDropzone: function(element) {
+ var i,
+ dzs = options.dropZoneElements;
+
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+qq.DragAndDrop.callbacks = function() {
+ return {
+ processingDroppedFiles: function() {},
+ processingDroppedFilesComplete: function(files) {},
+ dropError: function(code, errorSpecifics) {
+ qq.log("Drag & drop error code '" + code + " with these specifics: '" + errorSpecifics + "'", "error");
+ },
+ dropLog: function(message, level) {
+ qq.log(message, level);
+ }
+ }
+}
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file",
+ unsupportedBrowser: "Unrecoverable error - this browser does not permit file uploading of any kind."
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false,
+ prependFiles: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+
+ if (!qq.supportedFeatures.uploading || (this._options.cors.expected && !qq.supportedFeatures.uploadCors)) {
+ this._options.element.innerHTML = "" + this._options.messages.unsupportedBrowser + "
"
+ }
+ else {
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dropZoneElements = this._options.dragAndDrop.extraDropzones,
+ preventSelectFiles;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ dropZoneElements.push(this._find(this._options.element, 'drop'));
+ }
+
+ return new qq.DragAndDrop({
+ dropZoneElements: dropZoneElements,
+ hideDropZonesBeforeEnter: this._options.dragAndDrop.hideDropzones,
+ allowMultipleItems: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ processingDroppedFiles: function() {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ },
+ processingDroppedFilesComplete: function(files) {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ dropError: function(code, errorData) {
+ self._itemError(code, errorData);
+ },
+ dropLog: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type) {
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name) {
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ var onSuccessCallback = qq.bind(this._onSubmitDeleteSuccess, this, id);
+
+ qq.FineUploaderBasic.prototype._onSubmitDelete.call(this, id, onSuccessCallback);
+ },
+ _onSubmitDeleteSuccess: function(id) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ if (this._options.display.prependFiles) {
+ this._prependItem(item);
+ }
+ else {
+ this._listElement.appendChild(item);
+ }
+ this._filesInBatchAddedToUi += 1;
+
+ if (this._options.display.fileSizeOnSubmit && qq.supportedFeatures.ajaxUploading) {
+ this._displayFileSize(id);
+ }
+ },
+ _prependItem: function(item) {
+ var parentEl = this._listElement,
+ beforeEl = parentEl.firstChild;
+
+ if (this._totalFilesInBatch > 1 && this._filesInBatchAddedToUi > 0) {
+ beforeEl = qq(parentEl).children()[this._filesInBatchAddedToUi - 1].nextSibling;
+
+ }
+
+ parentEl.insertBefore(item, beforeEl);
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = item.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ },
+ _fileOrBlobRejected: function(id, name) {
+ this._totalFilesInBatch -= 1;
+ qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this, arguments);
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ this._totalFilesInBatch = items.length;
+ this._filesInBatchAddedToUi = 0;
+ qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this, arguments);
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, handlerImpl, api;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){},
+ onUuidChanged: function(id, newUuid){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.supportedFeatures.ajaxUploading) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, options.onUuidChanged, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, options.onUuidChanged, log);
+ }
+
+ function cancelSuccess(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ dequeue(id);
+ }
+
+
+ api = {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ handlerImpl.upload(id);
+ return true;
+ }
+
+ return false;
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ var cancelRetVal = handlerImpl.cancel(id);
+
+ if (qq.isPromise(cancelRetVal)) {
+ cancelRetVal.then(function() {
+ cancelSuccess(id);
+ });
+ }
+ else if (cancelRetVal !== false) {
+ cancelSuccess(id);
+ }
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ api.cancelAll();
+ queue = [];
+ handlerImpl.reset();
+ },
+ expunge: function(id) {
+ return handlerImpl.expunge(id);
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, onUuidChanged, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ formHandlerInstanceId = qq.getUniqueId(),
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var iframeName = iframe.id,
+ fileId = getFileIdForIframeName(iframeName);
+
+ onloadCallbacks[uuids[fileId]] = callback;
+
+ detachLoadEvents[fileId] = qq(iframe).attach('load', function() {
+ if (inputs[fileId]) {
+ log("Received iframe load event for CORS upload request (iframe name " + iframeName + ")");
+
+ postMessageCallbackTimers[iframeName] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for iframe name " + iframeName;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(iframeName, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = parseResponse(getFileIdForIframeName(iframeName), message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ log("Handling response for iframe name " + iframeName);
+ clearTimeout(postMessageCallbackTimers[iframeName]);
+ delete postMessageCallbackTimers[iframeName];
+
+ detachLoadEvent(iframeName);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(iframeName);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(id, iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHtml = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHtml);
+ //plain text response may be wrapped in tag
+ if (innerHtml && innerHtml.match(/^ ');
+
+ iframe.setAttribute('id', iframeName);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+ function expungeFile(id) {
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(getIframeName(id));
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ }
+
+ function getFileIdForIframeName(iframeName) {
+ return iframeName.split("_")[0];
+ }
+
+ function getIframeName(fileId) {
+ return fileId + "_" + formHandlerInstanceId;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ formHandlerInstanceId = qq.getUniqueId();
+ },
+ expunge: function(id) {
+ return expungeFile(id);
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, api.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeFile(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeFile(id);
+ return true;
+ }
+
+ return false;
+ },
+
+ upload: function(id) {
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, api.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(id, iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, onUuidChanged, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.supportedFeatures.chunking,
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.supportedFeatures.resume,
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+ options.onComplete(id, name, response, xhr);
+
+ if (fileState[id]) {
+ delete fileState[id].xhr;
+ }
+
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(id, xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+
+ if (response.newUuid !== undefined) {
+ log("Server requested UUID change from '" + fileState[id].uuid + "' to '" + response.newUuid + "'");
+ fileState[id].uuid = response.newUuid;
+ onUuidChanged(id, response.newUuid);
+ }
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(id, xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex) {
+ var currentChunkIndex;
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+
+ function handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex) {
+ var name = api.getName(id),
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part),
+ onResumeRetVal;
+
+ onResumeRetVal = options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume));
+ if (qq.isPromise(onResumeRetVal)) {
+ log("Waiting for onResume promise to be fulfilled for " + id);
+ onResumeRetVal.then(
+ function() {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ },
+ function() {
+ log("onResume promise fulfilled - failure indicated. Will not resume.")
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ );
+ }
+ else if (onResumeRetVal !== false) {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ }
+ else {
+ log("onResume callback returned false. Will not resume.");
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var firstChunkIndex = 0,
+ persistedChunkInfoForResume;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex);
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ uploadNextChunk(id);
+ }
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+ function expungeItem(id) {
+ var xhr = fileState[id].xhr;
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ }
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id, persistedChunkData,
+ uuid = qq.getUniqueId();
+
+ if (qq.isFile(fileOrBlobData)) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (qq.isBlob(fileOrBlobData.blob)) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ if (resumeEnabled) {
+ persistedChunkData = getPersistedChunkData(id);
+
+ if (persistedChunkData) {
+ uuid = persistedChunkData.uuid;
+ }
+ }
+
+ fileState[id].uuid = uuid;
+
+ return id;
+ },
+ getName: function(id){
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ expunge: function(id) {
+ return expungeItem(id);
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry) {
+ var name = this.getName(id);
+
+ if (this.isValid(id)) {
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ }
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, this.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeItem(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeItem(id);
+ return true;
+ }
+
+ return false;
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var uploader, $el, init, dataStore, pluginOption, pluginOptions, addCallbacks, transformVariables, isValidCommand,
+ delegateCommand;
+
+ pluginOptions = ['uploaderType'];
+
+ init = function (options) {
+ if (options) {
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+
+ if (pluginOption('uploaderType') === 'basic') {
+ uploader(new qq.FineUploaderBasic(xformedOpts));
+ }
+ else {
+ uploader(new qq.FineUploader(xformedOpts));
+ }
+ }
+
+ return $el;
+ };
+
+ dataStore = function(key, val) {
+ var data = $el.data('fineuploader');
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data('fineuploader', data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ //the underlying Fine Uploader instance is stored in jQuery's data stored, associated with the element
+ // tied to this instance of the plug-in
+ uploader = function(instanceToStore) {
+ return dataStore('uploader', instanceToStore);
+ };
+
+ pluginOption = function(option, optionVal) {
+ return dataStore(option, optionVal);
+ };
+
+ //implement all callbacks defined in Fine Uploader as functions that trigger appropriately names events and
+ // return the result of executing the bound handler back to Fine Uploader
+ addCallbacks = function(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {},
+ uploaderInst = new qq.FineUploaderBasic();
+
+ $.each(uploaderInst._options.callbacks, function(prop, func) {
+ var name, $callbackEl;
+
+ name = /^on(\w+)/.exec(prop)[1];
+ name = name.substring(0, 1).toLowerCase() + name.substring(1);
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var args = Array.prototype.slice.call(arguments);
+
+ return $callbackEl.triggerHandler(name, args);
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ transformVariables = function(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ if (source.uploaderType !== 'basic') {
+ xformed = { element : $el[0] };
+ }
+ else {
+ xformed = {};
+ }
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if ($.inArray(prop, pluginOptions) >= 0) {
+ pluginOption(prop, val);
+ }
+ else if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ isValidCommand = function(command) {
+ return $.type(command) === "string" &&
+ !command.match(/^_/) && //enforce private methods convention
+ uploader()[command] !== undefined;
+ };
+
+ //assuming we have already verified that this is a valid command, call the associated function in the underlying
+ // Fine Uploader instance (passing along the arguments from the caller) and return the result of the call back to the caller
+ delegateCommand = function(command) {
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
+
+ transformVariables(origArgs, xformedArgs);
+
+ return uploader()[command].apply(uploader(), xformedArgs);
+ };
+
+ $.fn.fineUploader = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (uploader() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error('Method ' + optionsOrCommand + ' does not exist on jQuery.fineUploader');
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
+/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var rootDataKey = "fineUploaderDnd",
+ $el;
+
+ function init (options) {
+ if (!options) {
+ options = {};
+ }
+
+ options.dropZoneElements = [$el];
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+ dnd(new qq.DragAndDrop(xformedOpts));
+
+ return $el;
+ };
+
+ function dataStore(key, val) {
+ var data = $el.data(rootDataKey);
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data(rootDataKey, data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ function dnd(instanceToStore) {
+ return dataStore('dndInstance', instanceToStore);
+ };
+
+ function addCallbacks(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {},
+ dndInst = new qq.FineUploaderBasic();
+
+ $.each(new qq.DragAndDrop.callbacks(), function(prop, func) {
+ var name = prop,
+ $callbackEl;
+
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var args = Array.prototype.slice.call(arguments),
+ jqueryHandlerResult = $callbackEl.triggerHandler(name, args);
+
+ return jqueryHandlerResult;
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ function transformVariables(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ xformed = {};
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ function isValidCommand(command) {
+ return $.type(command) === "string" &&
+ command === "dispose" &&
+ dnd()[command] !== undefined;
+ };
+
+ function delegateCommand(command) {
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
+ transformVariables(origArgs, xformedArgs);
+ return dnd()[command].apply(dnd(), xformedArgs);
+ };
+
+ $.fn.fineUploaderDnd = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (dnd() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error("Method " + optionsOrCommand + " does not exist in Fine Uploader's DnD module.");
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
diff --git a/ajax/libs/file-uploader/3.6.4/fineuploader-jquery.min.js b/ajax/libs/file-uploader/3.6.4/fineuploader-jquery.min.js
new file mode 100644
index 000000000..3027e17a4
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.4/fineuploader-jquery.min.js
@@ -0,0 +1,16 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: 3.6.4
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!=null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable&&!variable.nodeType&&Object.prototype.toString.call(variable)==="[object Object]"};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isArray=function(variable){"use strict";return Object.prototype.toString.call(variable)==="[object Array]"};qq.isString=function(maybeString){"use strict";return Object.prototype.toString.call(maybeString)==="[object String]"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFile=function(maybeFile){"use strict";return window.File&&Object.prototype.toString.call(maybeFile)==="[object File]"};qq.isFileList=function(maybeFileList){return window.FileList&&Object.prototype.toString.call(maybeFileList)==="[object FileList]"};qq.isFileOrInput=function(maybeFileOrInput){"use strict";return qq.isFile(maybeFileOrInput)||qq.isInput(maybeFileOrInput)};qq.isInput=function(maybeInput){if(window.HTMLInputElement){if(Object.prototype.toString.call(maybeInput)==="[object HTMLInputElement]"){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}if(maybeInput.tagName){if(maybeInput.tagName.toLowerCase()==="input"){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&Object.prototype.toString.call(maybeBlob)==="[object Blob]"};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice!==undefined||File.prototype.webkitSlice!==undefined||File.prototype.mozSlice!==undefined)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}});return first};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{defaultResponseError:"Upload failure reason unknown",sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"misc_data",paramNames:{name:"qqblobname"}},paste:{targetElement:null,defaultName:"pasted_image"},camera:{ios:false}};qq.extend(this._options,o,true);this._handleCameraAccess();this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData=this._createUploadDataTracker();this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}if(this._options.paste.targetElement){this._pasteHandler=this._createPasteHandler()}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader "+qq.version+"] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader "+qq.version+"] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},getNetUploads:function(){return this._netUploaded},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._netUploadedOrQueued++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset();this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData.reset();if(this._pasteHandler){this._pasteHandler.reset()}},addFiles:function(filesOrInputs,params,endpoint){var self=this,verifiedFilesOrInputs=[],fileOrInputIndex,fileOrInput,fileIndex;if(filesOrInputs){if(!qq.isFileList(filesOrInputs)){filesOrInputs=[].concat(filesOrInputs)}for(fileOrInputIndex=0;fileOrInputIndex=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||qq.supportedFeatures.deleteFileCors)},_onSubmitDelete:function(id,onSuccessCallback){if(this._isDeletePossible()){return this._handleCheckedCallback({name:"onSubmitDelete",callback:qq.bind(this._options.callbacks.onSubmitDelete,this,id),onSuccess:onSuccessCallback||qq.bind(this._deleteHandler.sendDelete,this,id,this.getUuid(id)),identifier:id})}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(id){this._uploadData.setStatus(id,qq.status.DELETING)},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);
+if(isError){this._uploadData.setStatus(id,qq.status.DELETE_FAILED);this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status,xhr)}else{this._uploadData.setStatus(id,qq.status.DELETED);this._netUploadedOrQueued--;this._netUploaded--;this._handler.expunge(id);this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){this._uploadData.setStatus(id,qq.status.UPLOADING)},_onInputChange:function(input){if(qq.supportedFeatures.ajaxUploading){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0&&this._netUploadedOrQueued+1>itemLimit){this._itemError("retryFailTooManyItems","");return false}this.log("Retrying upload for '"+fileName+"' (id: "+id+")...");this._filesInProgress.push(id);return true}else{this.log("'"+id+"' is not a valid file ID","error");return false}},_maybeParseAndSendUploadError:function(id,name,response,xhr){if(!response.success){if(xhr&&xhr.status!==200&&!response.error){this._options.callbacks.onError(id,name,"XHR returned response code "+xhr.status,xhr)}else{var errorReason=response.error?response.error:this._options.text.defaultResponseError;this._options.callbacks.onError(id,name,errorReason,xhr)}}},_prepareItemsForUpload:function(items,params,endpoint){var validationDescriptors=this._getValidationDescriptors(items);this._handleCheckedCallback({name:"onValidateBatch",callback:qq.bind(this._options.callbacks.onValidateBatch,this,validationDescriptors),onSuccess:qq.bind(this._onValidateBatchCallbackSuccess,this,validationDescriptors,items,params,endpoint),identifier:"batch validation"})},_upload:function(blobOrFileContainer,params,endpoint){var id=this._handler.add(blobOrFileContainer),name=this._handler.getName(id);this._uploadData.added(id);if(params){this.setParams(params,id)}if(endpoint){this.setEndpoint(endpoint,id)}this._handleCheckedCallback({name:"onSubmit",callback:qq.bind(this._options.callbacks.onSubmit,this,id,name),onSuccess:qq.bind(this._onSubmitCallbackSuccess,this,id,name),onFailure:qq.bind(this._fileOrBlobRejected,this,id,name),identifier:id})},_onSubmitCallbackSuccess:function(id,name){this._uploadData.setStatus(id,qq.status.SUBMITTED);this._onSubmit(id,name);this._options.callbacks.onSubmitted(id,name);if(this._options.autoUpload){if(!this._handler.upload(id)){this._uploadData.setStatus(id,qq.status.QUEUED)}}else{this._storeForLater(id)}},_storeForLater:function(id){this._storedIds.push(id)},_onValidateBatchCallbackSuccess:function(validationDescriptors,items,params,endpoint){var errorMessage,itemLimit=this._options.validation.itemLimit,proposedNetFilesUploadedOrQueued=this._netUploadedOrQueued+validationDescriptors.length;if(itemLimit===0||proposedNetFilesUploadedOrQueued<=itemLimit){if(items.length>0){this._handleCheckedCallback({name:"onValidate",callback:qq.bind(this._options.callbacks.onValidate,this,items[0]),onSuccess:qq.bind(this._onValidateCallbackSuccess,this,items,0,params,endpoint),onFailure:qq.bind(this._onValidateCallbackFailure,this,items,0,params,endpoint),identifier:"Item '"+items[0].name+"', size: "+items[0].size})}else{this._itemError("noFilesError","")}}else{errorMessage=this._options.messages.tooManyItemsError.replace(/\{netItems\}/g,proposedNetFilesUploadedOrQueued).replace(/\{itemLimit\}/g,itemLimit);this._batchError(errorMessage)}},_onValidateCallbackSuccess:function(items,index,params,endpoint){var nextIndex=index+1,validationDescriptor=this._getValidationDescriptor(items[index]),validItem=false;if(this._validateFileOrBlobData(items[index],validationDescriptor)){validItem=true;this._upload(items[index],params,endpoint)}this._maybeProcessNextItemAfterOnValidateCallback(validItem,items,nextIndex,params,endpoint)},_onValidateCallbackFailure:function(items,index,params,endpoint){var nextIndex=index+1;this._fileOrBlobRejected(undefined,items[0].name);this._maybeProcessNextItemAfterOnValidateCallback(false,items,nextIndex,params,endpoint)},_maybeProcessNextItemAfterOnValidateCallback:function(validItem,items,index,params,endpoint){var self=this;if(items.length>index){if(validItem||!this._options.validation.stopOnFirstInvalidFile){setTimeout(function(){var validationDescriptor=self._getValidationDescriptor(items[index]);self._handleCheckedCallback({name:"onValidate",callback:qq.bind(self._options.callbacks.onValidate,self,items[index]),onSuccess:qq.bind(self._onValidateCallbackSuccess,self,items,index,params,endpoint),onFailure:qq.bind(self._onValidateCallbackFailure,self,items,index,params,endpoint),identifier:"Item '"+validationDescriptor.name+"', size: "+validationDescriptor.size})},0)}}},_validateFileOrBlobData:function(item,validationDescriptor){var name=validationDescriptor.name,size=validationDescriptor.size,valid=true;if(this._options.callbacks.onValidate(validationDescriptor)===false){valid=false}if(qq.isFileOrInput(item)&&!this._isAllowedExtension(name)){this._itemError("typeError",name);valid=false}else if(size===0){this._itemError("emptyError",name);valid=false}else if(size&&this._options.validation.sizeLimit&&size>this._options.validation.sizeLimit){this._itemError("sizeError",name);valid=false}else if(size&&size999);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size!==undefined){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}},_handleCameraAccess:function(){if(this._options.camera.ios&&qq.ios()){this._options.multiple=false;if(this._options.validation.acceptFiles===null){this._options.validation.acceptFiles="image/*;capture=camera"}else{this._options.validation.acceptFiles+=",image/*;capture=camera"}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,droppedFiles=[],disposeSupport=new qq.DisposeSupport;options={dropZoneElements:[],hideDropZonesBeforeEnter:false,allowMultipleItems:true,classes:{dropActive:null},callbacks:new qq.DragAndDrop.callbacks};qq.extend(options,o,true);setupDragDrop();function uploadDroppedFiles(files){options.callbacks.dropLog("Grabbed "+files.length+" dropped files.");dz.dropDisabled(false);options.callbacks.processingDroppedFilesComplete(files)}function traverseFileTree(entry){var dirReader,i,parseEntryPromise=new qq.Promise;if(entry.isFile){entry.file(function(file){droppedFiles.push(file);parseEntryPromise.success()},function(fileError){options.callbacks.dropLog("Problem parsing '"+entry.fullPath+"'. FileError code "+fileError.code+".","error");parseEntryPromise.failure()})}else if(entry.isDirectory){dirReader=entry.createReader();dirReader.readEntries(function(entries){var entriesLeft=entries.length;for(i=0;i1&&!options.allowMultipleItems){options.callbacks.processingDroppedFilesComplete([]);options.callbacks.dropError("tooManyFilesError","");dz.dropDisabled(false);handleDataTransferPromise.failure()}else{droppedFiles=[];if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file",unsupportedBrowser:"Unrecoverable error - this browser does not permit file uploading of any kind."},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false,prependFiles:false},paste:{promptForName:false,namePromptMessage:"Please name this image"},showMessage:function(message){setTimeout(function(){window.alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=window.confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)},showPrompt:function(message,defaultValue){var promise=new qq.Promise,retVal=window.prompt(message,defaultValue);if(retVal!=null&&qq.trimStr(retVal).length>0){promise.success(retVal)}else{promise.failure("Undefined or invalid user-supplied value.")}return promise}},true);qq.extend(this._options,o,true);if(!qq.supportedFeatures.uploading||this._options.cors.expected&&!qq.supportedFeatures.uploadCors){this._options.element.innerHTML=""+this._options.messages.unsupportedBrowser+"
"}else{this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop();if(this._options.paste.targetElement&&this._options.paste.promptForName){this._setupPastePrompt()}this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0}};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop();this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dropZoneElements=this._options.dragAndDrop.extraDropzones,preventSelectFiles;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){dropZoneElements.push(this._find(this._options.element,"drop"))}return new qq.DragAndDrop({dropZoneElements:dropZoneElements,hideDropZonesBeforeEnter:this._options.dragAndDrop.hideDropzones,allowMultipleItems:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{processingDroppedFiles:function(){var input=self._button.getInput();qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)},processingDroppedFilesComplete:function(files){var input=self._button.getInput();qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles);if(files){self.addFiles(files)}},dropError:function(code,errorData){self._itemError(code,errorData)},dropLog:function(message,level){self.log(message,level)}}})},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){var item=this.getItemByFileId(id);if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}else{qq(item).addClass(this._classes.retryable);return false}},_onSubmitDelete:function(id){var onSuccessCallback=qq.bind(this._onSubmitDeleteSuccess,this,id);qq.FineUploaderBasic.prototype._onSubmitDelete.call(this,id,onSuccessCallback)},_onSubmitDeleteSuccess:function(id){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}if(this._options.display.prependFiles){this._prependItem(item)}else{this._listElement.appendChild(item)}this._filesInBatchAddedToUi+=1;if(this._options.display.fileSizeOnSubmit&&qq.supportedFeatures.ajaxUploading){this._displayFileSize(id)}},_prependItem:function(item){var parentEl=this._listElement,beforeEl=parentEl.firstChild;if(this._totalFilesInBatch>1&&this._filesInBatchAddedToUi>0){beforeEl=qq(parentEl).children()[this._filesInBatchAddedToUi-1].nextSibling}parentEl.insertBefore(item,beforeEl)},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=item.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)
+}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_itemError:function(code,name){var message=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(message)},_batchError:function(message){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments);this._options.showMessage(message)},_setupPastePrompt:function(){var self=this;this._options.callbacks.onPasteReceived=function(){var message=self._options.paste.namePromptMessage,defaultVal=self._options.paste.defaultName;return self._options.showPrompt(message,defaultVal)}},_fileOrBlobRejected:function(id,name){this._totalFilesInBatch-=1;qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this,arguments)},_prepareItemsForUpload:function(items,params,endpoint){this._totalFilesInBatch=items.length;this._filesInBatchAddedToUi=0;qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this,arguments)}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,handlerImpl,api;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){},onUuidChanged:function(id,newUuid){}};qq.extend(options,o);log=options.log;function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){var cancelRetVal=handlerImpl.cancel(id);if(qq.isPromise(cancelRetVal)){cancelRetVal.then(function(){cancelSuccess(id)})}else if(cancelRetVal!==false){cancelSuccess(id)}},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},reset:function(){log("Resetting upload handler");api.cancelAll();queue=[];handlerImpl.reset()},expunge:function(id){return handlerImpl.expunge(id)},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}};return api};qq.UploadHandlerForm=function(o,uploadCompleteCallback,onUuidChanged,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},formHandlerInstanceId=qq.getUniqueId(),api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var iframeName=iframe.id,fileId=getFileIdForIframeName(iframeName);onloadCallbacks[uuids[fileId]]=callback;detachLoadEvents[fileId]=qq(iframe).attach("load",function(){if(inputs[fileId]){log("Received iframe load event for CORS upload request (iframe name "+iframeName+")");postMessageCallbackTimers[iframeName]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for iframe name "+iframeName;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(iframeName,function(message){log("Received the following window message: '"+message+"'");var response=parseResponse(getFileIdForIframeName(iframeName),message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){log("Handling response for iframe name "+iframeName);clearTimeout(postMessageCallbackTimers[iframeName]);delete postMessageCallbackTimers[iframeName];detachLoadEvent(iframeName);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(iframeName);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(id,iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHtml=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHtml);if(innerHtml&&innerHtml.match(/^ ');iframe.setAttribute("id",iframeName);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}function expungeFile(id){delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(getIframeName(id));if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}}function getFileIdForIframeName(iframeName){return iframeName.split("_")[0]}function getIframeName(fileId){return fileId+"_"+formHandlerInstanceId}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){if(api.isValid(id)){return inputs[id].value.replace(/.*(\/|\\)/,"")}else{log(id+" is not a valid item ID.","error")}},isValid:function(id){return inputs[id]!==undefined},reset:function(){inputs=[];uuids=[];detachLoadEvents={};formHandlerInstanceId=qq.getUniqueId()},expunge:function(id){return expungeFile(id)},getUuid:function(id){return uuids[id]},cancel:function(id){var onCancelRetVal=options.onCancel(id,api.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeFile(id)})}else if(onCancelRetVal!==false){expungeFile(id);return true}return false},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,api.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(id,iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove()}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,onUuidChanged,logCallback){"use strict";var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.supportedFeatures.chunking,resumeEnabled=options.resume.enabled&&chunkFiles&&qq.supportedFeatures.resume,resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);if(fileState[id]){delete fileState[id].xhr}uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(id,xhr){var response;try{response=qq.parseJson(xhr.responseText);if(response.newUuid!==undefined){log("Server requested UUID change from '"+fileState[id].uuid+"' to '"+response.newUuid+"'");fileState[id].uuid=response.newUuid;onUuidChanged(id,response.newUuid)}}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(id,xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex){var currentChunkIndex;for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}uploadNextChunk(id)}function onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex);calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}function handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex){var name=api.getName(id),firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part),onResumeRetVal;onResumeRetVal=options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume));if(qq.isPromise(onResumeRetVal)){log("Waiting for onResume promise to be fulfilled for "+id);onResumeRetVal.then(function(){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)},function(){log("onResume promise fulfilled - failure indicated. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)})}else if(onResumeRetVal!==false){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)}else{log("onResume callback returned false. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}function handleFileChunkingUpload(id,retry){var firstChunkIndex=0,persistedChunkInfoForResume;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex)}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{uploadNextChunk(id)}}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}function expungeItem(id){var xhr=fileState[id].xhr;if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]}api={add:function(fileOrBlobData){var id,persistedChunkData,uuid=qq.getUniqueId();if(qq.isFile(fileOrBlobData)){id=fileState.push({file:fileOrBlobData})-1}else if(qq.isBlob(fileOrBlobData.blob)){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}if(resumeEnabled){persistedChunkData=getPersistedChunkData(id);if(persistedChunkData){uuid=persistedChunkData.uuid}}fileState[id].uuid=uuid;return id},getName:function(id){if(api.isValid(id)){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}}else{log(id+" is not a valid item ID.","error")}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},expunge:function(id){return expungeItem(id)},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);if(this.isValid(id)){options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}}},cancel:function(id){var onCancelRetVal=options.onCancel(id,this.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeItem(id)})}else if(onCancelRetVal!==false){expungeItem(id);return true}return false},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};!function($){"use strict";var uploader,$el,init,dataStore,pluginOption,pluginOptions,addCallbacks,transformVariables,isValidCommand,delegateCommand;pluginOptions=["uploaderType"];init=function(options){if(options){var xformedOpts=transformVariables(options);addCallbacks(xformedOpts);if(pluginOption("uploaderType")==="basic"){uploader(new qq.FineUploaderBasic(xformedOpts))}else{uploader(new qq.FineUploader(xformedOpts))}}return $el};dataStore=function(key,val){var data=$el.data("fineuploader");if(val){if(data===undefined){data={}}data[key]=val;$el.data("fineuploader",data)}else{if(data===undefined){return null}return data[key]}};uploader=function(instanceToStore){return dataStore("uploader",instanceToStore)};pluginOption=function(option,optionVal){return dataStore(option,optionVal)};addCallbacks=function(transformedOpts){var callbacks=transformedOpts.callbacks={},uploaderInst=new qq.FineUploaderBasic;$.each(uploaderInst._options.callbacks,function(prop,func){var name,$callbackEl;name=/^on(\w+)/.exec(prop)[1];name=name.substring(0,1).toLowerCase()+name.substring(1);$callbackEl=$el;callbacks[prop]=function(){var args=Array.prototype.slice.call(arguments);return $callbackEl.triggerHandler(name,args)}})};transformVariables=function(source,dest){var xformed,arrayVals;if(dest===undefined){if(source.uploaderType!=="basic"){xformed={element:$el[0]}}else{xformed={}}}else{xformed=dest}$.each(source,function(prop,val){if($.inArray(prop,pluginOptions)>=0){pluginOption(prop,val)}else if(val instanceof $){xformed[prop]=val[0]}else if($.isPlainObject(val)){xformed[prop]={};transformVariables(val,xformed[prop])}else if($.isArray(val)){arrayVals=[];$.each(val,function(idx,arrayVal){if(arrayVal instanceof $){$.merge(arrayVals,arrayVal)}else{arrayVals.push(arrayVal)}});xformed[prop]=arrayVals}else{xformed[prop]=val}});if(dest===undefined){return xformed}};isValidCommand=function(command){return $.type(command)==="string"&&!command.match(/^_/)&&uploader()[command]!==undefined};delegateCommand=function(command){var xformedArgs=[],origArgs=Array.prototype.slice.call(arguments,1);transformVariables(origArgs,xformedArgs);return uploader()[command].apply(uploader(),xformedArgs)};$.fn.fineUploader=function(optionsOrCommand){var self=this,selfArgs=arguments,retVals=[];this.each(function(index,el){$el=$(el);if(uploader()&&isValidCommand(optionsOrCommand)){retVals.push(delegateCommand.apply(self,selfArgs));if(self.length===1){return false}}else if(typeof optionsOrCommand==="object"||!optionsOrCommand){init.apply(self,selfArgs)
+}else{$.error("Method "+optionsOrCommand+" does not exist on jQuery.fineUploader")}});if(retVals.length===1){return retVals[0]}else if(retVals.length>1){return retVals}return this}}(jQuery);!function($){"use strict";var rootDataKey="fineUploaderDnd",$el;function init(options){if(!options){options={}}options.dropZoneElements=[$el];var xformedOpts=transformVariables(options);addCallbacks(xformedOpts);dnd(new qq.DragAndDrop(xformedOpts));return $el}function dataStore(key,val){var data=$el.data(rootDataKey);if(val){if(data===undefined){data={}}data[key]=val;$el.data(rootDataKey,data)}else{if(data===undefined){return null}return data[key]}}function dnd(instanceToStore){return dataStore("dndInstance",instanceToStore)}function addCallbacks(transformedOpts){var callbacks=transformedOpts.callbacks={},dndInst=new qq.FineUploaderBasic;$.each(new qq.DragAndDrop.callbacks,function(prop,func){var name=prop,$callbackEl;$callbackEl=$el;callbacks[prop]=function(){var args=Array.prototype.slice.call(arguments),jqueryHandlerResult=$callbackEl.triggerHandler(name,args);return jqueryHandlerResult}})}function transformVariables(source,dest){var xformed,arrayVals;if(dest===undefined){xformed={}}else{xformed=dest}$.each(source,function(prop,val){if(val instanceof $){xformed[prop]=val[0]}else if($.isPlainObject(val)){xformed[prop]={};transformVariables(val,xformed[prop])}else if($.isArray(val)){arrayVals=[];$.each(val,function(idx,arrayVal){if(arrayVal instanceof $){$.merge(arrayVals,arrayVal)}else{arrayVals.push(arrayVal)}});xformed[prop]=arrayVals}else{xformed[prop]=val}});if(dest===undefined){return xformed}}function isValidCommand(command){return $.type(command)==="string"&&command==="dispose"&&dnd()[command]!==undefined}function delegateCommand(command){var xformedArgs=[],origArgs=Array.prototype.slice.call(arguments,1);transformVariables(origArgs,xformedArgs);return dnd()[command].apply(dnd(),xformedArgs)}$.fn.fineUploaderDnd=function(optionsOrCommand){var self=this,selfArgs=arguments,retVals=[];this.each(function(index,el){$el=$(el);if(dnd()&&isValidCommand(optionsOrCommand)){retVals.push(delegateCommand.apply(self,selfArgs));if(self.length===1){return false}}else if(typeof optionsOrCommand==="object"||!optionsOrCommand){init.apply(self,selfArgs)}else{$.error("Method "+optionsOrCommand+" does not exist in Fine Uploader's DnD module.")}});if(retVals.length===1){return retVals[0]}else if(retVals.length>1){return retVals}return this}}(jQuery);
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.4/fineuploader.css b/ajax/libs/file-uploader/3.6.4/fineuploader.css
new file mode 100644
index 000000000..11a436a06
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.4/fineuploader.css
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2013, Widen Enterprises info@fineuploader.com
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+.qq-uploader {
+ position: relative;
+ width: 100%;
+}
+.qq-upload-button {
+ display: block;
+ width: 105px;
+ padding: 7px 0;
+ text-align: center;
+ background: #880000;
+ border-bottom: 1px solid #DDD;
+ color: #FFF;
+}
+.qq-upload-button-hover {
+ background: #CC0000;
+}
+.qq-upload-button-focus {
+ outline: 1px dotted #000000;
+}
+.qq-upload-drop-area, .qq-upload-extra-drop-area {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ min-height: 30px;
+ z-index: 2;
+ background: #FF9797;
+ text-align: center;
+}
+.qq-upload-drop-area span {
+ display: block;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ margin-top: -8px;
+ font-size: 16px;
+}
+.qq-upload-extra-drop-area {
+ position: relative;
+ margin-top: 50px;
+ font-size: 16px;
+ padding-top: 30px;
+ height: 20px;
+ min-height: 40px;
+}
+.qq-upload-drop-area-active {
+ background: #FF7171;
+}
+.qq-upload-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+.qq-upload-list li {
+ margin: 0;
+ padding: 9px;
+ line-height: 15px;
+ font-size: 16px;
+ background-color: #FFF0BD;
+}
+.qq-upload-file, .qq-upload-spinner, .qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text, .qq-upload-finished, .qq-upload-delete {
+ margin-right: 12px;
+}
+.qq-upload-file {
+}
+.qq-upload-spinner {
+ display: inline-block;
+ background: url("loading.gif");
+ width: 15px;
+ height: 15px;
+ vertical-align: text-bottom;
+}
+.qq-drop-processing {
+ display: none;
+}
+.qq-drop-processing-spinner {
+ display: inline-block;
+ background: url("processing.gif");
+ width: 24px;
+ height: 24px;
+ vertical-align: text-bottom;
+}
+.qq-upload-finished {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-retry, .qq-upload-delete {
+ display: none;
+ color: #000000;
+}
+.qq-upload-cancel, .qq-upload-delete {
+ color: #000000;
+}
+.qq-upload-retryable .qq-upload-retry {
+ display: inline;
+}
+.qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-delete {
+ font-size: 12px;
+ font-weight: normal;
+}
+.qq-upload-failed-text {
+ display: none;
+ font-style: italic;
+ font-weight: bold;
+}
+.qq-upload-failed-icon {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-fail .qq-upload-failed-text {
+ display: inline;
+}
+.qq-upload-retrying .qq-upload-failed-text {
+ display: inline;
+ color: #D60000;
+}
+.qq-upload-list li.qq-upload-success {
+ background-color: #5DA30C;
+ color: #FFFFFF;
+}
+.qq-upload-list li.qq-upload-fail {
+ background-color: #D60000;
+ color: #FFFFFF;
+}
+.qq-progress-bar {
+ background: -moz-linear-gradient(top, rgba(30,87,153,1) 0%, rgba(41,137,216,1) 50%, rgba(32,124,202,1) 51%, rgba(125,185,232,1) 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(30,87,153,1)), color-stop(50%,rgba(41,137,216,1)), color-stop(51%,rgba(32,124,202,1)), color-stop(100%,rgba(125,185,232,1))); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* IE10+ */
+ background: linear-gradient(to bottom, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* W3C */
+ width: 0%;
+ height: 15px;
+ border-radius: 6px;
+ margin-bottom: 3px;
+ display: none;
+}
diff --git a/ajax/libs/file-uploader/3.6.4/fineuploader.js b/ajax/libs/file-uploader/3.6.4/fineuploader.js
new file mode 100644
index 000000000..8f687c98c
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.4/fineuploader.js
@@ -0,0 +1,4724 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: 3.6.4
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity != null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable && !variable.nodeType && Object.prototype.toString.call(variable) === '[object Object]';
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isArray = function(variable) {
+ "use strict";
+ return Object.prototype.toString.call(variable) === "[object Array]";
+}
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+qq.isFile = function(maybeFile) {
+ "use strict";
+
+ return window.File && Object.prototype.toString.call(maybeFile) === '[object File]'
+};
+
+qq.isFileList = function(maybeFileList) {
+ return window.FileList && Object.prototype.toString.call(maybeFileList) === '[object FileList]'
+}
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+
+ return qq.isFile(maybeFileOrInput) || qq.isInput(maybeFileOrInput);
+};
+
+qq.isInput = function(maybeInput) {
+ if (window.HTMLInputElement) {
+ if (Object.prototype.toString.call(maybeInput) === '[object HTMLInputElement]') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ if (maybeInput.tagName) {
+ if (maybeInput.tagName.toLowerCase() === 'input') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && Object.prototype.toString.call(maybeBlob) === '[object Blob]';
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice !== undefined || File.prototype.webkitSlice !== undefined || File.prototype.mozSlice !== undefined);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+
+ return first;
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+qq.ios = function() {
+ "use strict";
+ return navigator.userAgent.indexOf("iPad") !== -1
+ || navigator.userAgent.indexOf("iPod") !== -1
+ || navigator.userAgent.indexOf("iPhone") !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(objOrArray, callback) {
+ "use strict";
+ var keyOrIndex, retVal;
+ if (objOrArray) {
+ if (qq.isArray(objOrArray)) {
+ for (keyOrIndex = 0; keyOrIndex < objOrArray.length; keyOrIndex++) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ else {
+ for (keyOrIndex in objOrArray) {
+ if (Object.prototype.hasOwnProperty.call(objOrArray, keyOrIndex)) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+ }
+};
+
+//include any args that should be passed to the new function after the context arg
+qq.bind = function(oldFunc, context) {
+ if (qq.isFunction(oldFunc)) {
+ var args = Array.prototype.slice.call(arguments, 2);
+
+ return function() {
+ if (arguments.length) {
+ args = args.concat(Array.prototype.slice.call(arguments))
+ }
+ return oldFunc.apply(context, args);
+ };
+ }
+
+ throw new Error("first parameter must be a function!");
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ cookie;
+
+ qq.each(ca, function(idx, part) {
+ var cookiePart = part;
+ while (cookiePart.charAt(0)==' ') {
+ cookiePart = cookiePart.substring(1, cookiePart.length);
+ }
+
+ if (cookiePart.indexOf(nameEQ) === 0) {
+ cookie = cookiePart.substring(nameEQ.length, cookiePart.length);
+ return false;
+ }
+ });
+
+ return cookie;
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+qq.version="-unstable-";qq.supportedFeatures = (function() {
+ var supportsUploading,
+ supportsAjaxFileUploading,
+ supportsFolderDrop,
+ supportsChunking,
+ supportsResume,
+ supportsUploadViaPaste,
+ supportsUploadCors,
+ supportsDeleteFileCors;
+
+
+ function testSupportsFileInputElement() {
+ var supported = true,
+ tempInput;
+
+ try {
+ tempInput = document.createElement('input');
+ tempInput.type = 'file';
+ qq(tempInput).hide();
+
+ if(tempInput.disabled) {
+ supported = false;
+ }
+ }
+ catch(ex) {
+ supported = false;
+ }
+
+ return supported;
+ }
+
+ //only way to test for Filesystem API support since webkit does not expose the DataTransfer interface
+ function isChrome21OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[2][1-9]|Chrome\/[3-9][0-9]/) !== undefined;
+ }
+
+ //only way to test for complete Clipboard API support at this time
+ function isChrome14OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/) !== undefined;
+ }
+
+
+ supportsUploading = testSupportsFileInputElement();
+
+ supportsAjaxFileUploading = supportsUploading && qq.isXhrUploadSupported();
+
+ supportsFolderDrop = supportsAjaxFileUploading && isChrome21OrHigher();
+
+ supportsChunking = supportsAjaxFileUploading && qq.isFileChunkingSupported();
+
+ supportsResume = supportsAjaxFileUploading && supportsChunking && qq.areCookiesEnabled();
+
+ supportsUploadViaPaste = supportsAjaxFileUploading && isChrome14OrHigher();
+
+ supportsUploadCors = supportsUploading && (window.postMessage !== undefined || supportsAjaxFileUploading);
+
+ supportsDeleteFileCors = supportsAjaxFileUploading;
+
+
+ return {
+ uploading: supportsUploading,
+ ajaxUploading: supportsAjaxFileUploading,
+ fileDrop: supportsAjaxFileUploading, //NOTE: will also return true for touch-only devices. It's not currently possible to accurately test for touch-only devices
+ folderDrop: supportsFolderDrop,
+ chunking: supportsChunking,
+ resume: supportsResume,
+ uploadCustomHeaders: supportsAjaxFileUploading,
+ uploadNonMultipart: supportsAjaxFileUploading,
+ itemSizeValidation: supportsAjaxFileUploading,
+ uploadViaPaste: supportsUploadViaPaste,
+ progressBar: supportsAjaxFileUploading,
+ uploadCors: supportsUploadCors,
+ deleteFileCors: supportsDeleteFileCors,
+ canDetermineSize: supportsAjaxFileUploading
+ }
+
+}());
+/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallbacks = [],
+ failureCallbacks = [],
+ doneCallbacks = [],
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ if (onSuccess) {
+ successCallbacks.push(onSuccess);
+ }
+ if (onFailure) {
+ failureCallbacks.push(onFailure);
+ }
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+
+ return this;
+ },
+
+ done: function(callback) {
+ if (state === 0) {
+ doneCallbacks.push(callback);
+ }
+ else {
+ callback();
+ }
+
+ return this;
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallbacks.length) {
+ qq.each(successCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallbacks.length) {
+ qq.each(failureCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ }
+ };
+};
+
+qq.isPromise = function(maybePromise) {
+ return maybePromise && maybePromise.then && maybePromise.done;
+};/*globals qq*/
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ disposeSupport = new qq.DisposeSupport(),
+ options = {
+ element: null,
+ // if set to true adds multiple attribute to file input
+ multiple: false,
+ acceptFiles: null,
+ // name attribute of file input
+ name: 'file',
+ onChange: function(input) {},
+ hoverClass: 'qq-upload-button-hover',
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent){
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+
+ qq.extend(options, o);
+
+ // make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side
+ // in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};qq.UploadData = function(uploaderProxy) {
+ var data = [],
+ byId = {},
+ byUuid = {},
+ byStatus = {},
+ api;
+
+ function getDataByIds(ids) {
+ if (qq.isArray(ids)) {
+ var entries = [];
+
+ qq.each(ids, function(idx, id) {
+ entries.push(data[byId[id]]);
+ });
+
+ return entries;
+ }
+
+ return data[byId[ids]];
+ }
+
+ function getDataByUuids(uuids) {
+ if (qq.isArray(uuids)) {
+ var entries = [];
+
+ qq.each(uuids, function(idx, uuid) {
+ entries.push(data[byUuid[uuid]]);
+ });
+
+ return entries;
+ }
+
+ return data[byUuid[uuids]];
+ }
+
+ function getDataByStatus(status) {
+ var statusResults = [],
+ statuses = [].concat(status);
+
+ qq.each(statuses, function(index, statusEnum) {
+ var statusResultIndexes = byStatus[statusEnum];
+
+ if (statusResultIndexes !== undefined) {
+ qq.each(statusResultIndexes, function(i, dataIndex) {
+ statusResults.push(data[dataIndex]);
+ });
+ }
+ });
+
+ return statusResults;
+ }
+
+ api = {
+ added: function(id) {
+ var uuid = uploaderProxy.getUuid(id),
+ name = uploaderProxy.getName(id),
+ size = uploaderProxy.getSize(id),
+ status = qq.status.SUBMITTING;
+
+ var index = data.push({
+ id: id,
+ name: name,
+ uuid: uuid,
+ size: size,
+ status: status
+ }) - 1;
+
+ byId[id] = index;
+
+ byUuid[uuid] = index;
+
+ if (byStatus[status] === undefined) {
+ byStatus[status] = [];
+ }
+ byStatus[status].push(index);
+
+ uploaderProxy.onStatusChange(id, undefined, status);
+ },
+
+ retrieve: function(optionalFilter) {
+ if (qq.isObject(optionalFilter) && data.length) {
+ if (optionalFilter.id !== undefined) {
+ return getDataByIds(optionalFilter.id);
+ }
+
+ else if (optionalFilter.uuid !== undefined) {
+ return getDataByUuids(optionalFilter.uuid);
+ }
+
+ else if (optionalFilter.status) {
+ return getDataByStatus(optionalFilter.status);
+ }
+ }
+ else {
+ return qq.extend([], data, true);
+ }
+ },
+
+ reset: function() {
+ data = [];
+ byId = {};
+ byUuid = {};
+ byStatus = {};
+ },
+
+ setStatus: function(id, newStatus) {
+ var dataIndex = byId[id],
+ oldStatus = data[dataIndex].status,
+ byStatusOldStatusIndex = qq.indexOf(byStatus[oldStatus], dataIndex);
+
+ byStatus[oldStatus].splice(byStatusOldStatusIndex, 1);
+
+ data[dataIndex].status = newStatus;
+
+ if (byStatus[newStatus] === undefined) {
+ byStatus[newStatus] = [];
+ }
+ byStatus[newStatus].push(dataIndex);
+
+ uploaderProxy.onStatusChange(id, oldStatus, newStatus);
+ },
+
+ uuidChanged: function(id, newUuid) {
+ var dataIndex = byId[id],
+ oldUuid = data[dataIndex].uuid;
+
+ data[dataIndex].uuid = newUuid;
+ byUuid[newUuid] = dataIndex;
+ delete byUuid[oldUuid];
+ }
+ };
+
+ return api;
+};
+
+qq.status = {
+ SUBMITTING: "submitting",
+ SUBMITTED: "submitted",
+ REJECTED: "rejected",
+ QUEUED: "queued",
+ CANCELED: "canceled",
+ UPLOADING: "uploading",
+ UPLOAD_RETRYING: "retrying upload",
+ UPLOAD_SUCCESSFUL: "upload successful",
+ UPLOAD_FAILED: "upload failed",
+ DELETE_FAILED: "delete failed",
+ DELETING: "deleting",
+ DELETED: "deleted"
+};qq.FineUploaderBasic = function(o) {
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true,
+ acceptFiles: null
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON, maybeXhr){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhr, isError){},
+ onPasteReceived: function(blob) {},
+ onStatusChange: function(id, oldStatus, newStatus) {}
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ defaultResponseError: "Upload failure reason unknown",
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ blobs: {
+ defaultName: 'misc_data',
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ },
+ camera: {
+ ios: false
+ }
+ };
+
+ qq.extend(this._options, o, true);
+
+ this._handleCameraAccess();
+
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData = this._createUploadDataTracker();
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader ' + qq.version + '] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader ' + qq.version + '] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function() {
+ return this._filesInProgress.length;
+ },
+ getNetUploads: function() {
+ return this._netUploaded;
+ },
+ uploadStoredFiles: function(){
+ "use strict";
+ var idToUpload;
+
+ while(this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netUploadedOrQueued++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData.reset();
+
+ if (this._pasteHandler) {
+ this._pasteHandler.reset();
+ }
+ },
+ addFiles: function(filesOrInputs, params, endpoint) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ fileOrInputIndex, fileOrInput, fileIndex;
+
+ if (filesOrInputs) {
+ if (!qq.isFileList(filesOrInputs)) {
+ filesOrInputs = [].concat(filesOrInputs);
+ }
+
+ for (fileOrInputIndex = 0; fileOrInputIndex < filesOrInputs.length; fileOrInputIndex+=1) {
+ fileOrInput = filesOrInputs[fileOrInputIndex];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ if (qq.isInput(fileOrInput) && qq.supportedFeatures.ajaxUploading) {
+ for (fileIndex = 0; fileIndex < fileOrInput.files.length; fileIndex++) {
+ verifiedFilesOrInputs.push(fileOrInput.files[fileIndex]);
+ }
+ }
+ else {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Received ' + verifiedFilesOrInputs.length + ' files or inputs.');
+ this._prepareItemsForUpload(verifiedFilesOrInputs, params, endpoint);
+ }
+ },
+ addBlobs: function(blobDataOrArray, params, endpoint) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._prepareItemsForUpload(verifiedBlobDataList, params, endpoint);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ doesExist: function(fileOrBlobId) {
+ return this._handler.isValid(fileOrBlobId);
+ },
+ getUploads: function(optionalFilter) {
+ return this._uploadData.retrieve(optionalFilter);
+ },
+ _handleCheckedCallback: function(details) {
+ var self = this,
+ callbackRetVal = details.callback();
+
+ if (qq.isPromise(callbackRetVal)) {
+ this.log(details.name + " - waiting for " + details.name + " promise to be fulfilled for " + details.identifier);
+ return callbackRetVal.then(
+ function(successParam) {
+ self.log(details.name + " promise success for " + details.identifier);
+ details.onSuccess(successParam);
+ },
+ function() {
+ if (details.onFailure) {
+ self.log(details.name + " promise failure for " + details.identifier);
+ details.onFailure();
+ }
+ else {
+ self.log(details.name + " promise failure for " + details.identifier);
+ }
+ });
+ }
+
+ if (callbackRetVal !== false) {
+ details.onSuccess(callbackRetVal);
+ }
+ else {
+ if (details.onFailure) {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Invoking failure callback.")
+ details.onFailure();
+ }
+ else {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Will not proceed.")
+ }
+ }
+
+ return callbackRetVal;
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.supportedFeatures.ajaxUploading,
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result, xhr);
+ },
+ onCancel: function(id, name) {
+ return self._handleCheckedCallback({
+ name: "onCancel",
+ callback: qq.bind(self._options.callbacks.onCancel, self, id, name),
+ onSuccess: qq.bind(self._onCancel, self, id, name),
+ identifier: id
+ });
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ onUuidChanged: function(id, newUuid) {
+ self._uploadData.uuidChanged(id, newUuid);
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhr, isError) {
+ self._onDeleteComplete(id, xhr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ self._handleCheckedCallback({
+ name: "onPasteReceived",
+ callback: qq.bind(self._options.callbacks.onPasteReceived, self, blob),
+ onSuccess: qq.bind(self._handlePasteSuccess, self, blob),
+ identifier: "pasted image"
+ });
+ }
+ }
+ });
+ },
+ _createUploadDataTracker: function() {
+ var self = this;
+
+ return new qq.UploadData({
+ getName: function(id) {
+ return self.getName(id);
+ },
+ getUuid: function(id) {
+ return self.getUuid(id);
+ },
+ getSize: function(id) {
+ return self.getSize(id);
+ },
+ onStatusChange: function(id, oldStatus, newStatus) {
+ self._options.callbacks.onStatusChange(id, oldStatus, newStatus);
+ }
+ });
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total) {
+ //nothing to do yet in core uploader
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_FAILED);
+ this._netUploadedOrQueued--;
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.UPLOAD_SUCCESSFUL);
+ this._netUploaded++;
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.CANCELED);
+
+ this._netUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+ },
+ _isDeletePossible: function() {
+ return (this._options.deleteFile.enabled &&
+ (!this._options.cors.expected || qq.supportedFeatures.deleteFileCors));
+ },
+ _onSubmitDelete: function(id, onSuccessCallback) {
+ if (this._isDeletePossible()) {
+ return this._handleCheckedCallback({
+ name: "onSubmitDelete",
+ callback: qq.bind(this._options.callbacks.onSubmitDelete, this, id),
+ onSuccess: onSuccessCallback || qq.bind(this._deleteHandler.sendDelete, this, id, this.getUuid(id)),
+ identifier: id
+ });
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(id) {
+ this._uploadData.setStatus(id, qq.status.DELETING);
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this._uploadData.setStatus(id, qq.status.DELETE_FAILED);
+ this.log("Delete request for '" + name + "' has failed.", "error");
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhr.status, xhr);
+ }
+ else {
+ this._uploadData.setStatus(id, qq.status.DELETED);
+ this._netUploadedOrQueued--;
+ this._netUploaded--;
+ this._handler.expunge(id);
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.UPLOADING);
+ },
+ _onInputChange: function(input){
+ if (qq.supportedFeatures.ajaxUploading) {
+ this.addFiles(input.files);
+ }
+ else {
+ this.addFiles(input);
+ }
+
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems", "");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : this._options.text.defaultResponseError;
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ var validationDescriptors = this._getValidationDescriptors(items);
+
+ this._handleCheckedCallback({
+ name: "onValidateBatch",
+ callback: qq.bind(this._options.callbacks.onValidateBatch, this, validationDescriptors),
+ onSuccess: qq.bind(this._onValidateBatchCallbackSuccess, this, validationDescriptors, items, params, endpoint),
+ identifier: "batch validation"
+ });
+ },
+ _upload: function(blobOrFileContainer, params, endpoint) {
+ var id = this._handler.add(blobOrFileContainer),
+ name = this._handler.getName(id);
+
+ this._uploadData.added(id);
+
+ if (params) {
+ this.setParams(params, id);
+ }
+
+ if (endpoint) {
+ this.setEndpoint(endpoint, id);
+ }
+
+ this._handleCheckedCallback({
+ name: "onSubmit",
+ callback: qq.bind(this._options.callbacks.onSubmit, this, id, name),
+ onSuccess: qq.bind(this._onSubmitCallbackSuccess, this, id, name),
+ onFailure: qq.bind(this._fileOrBlobRejected, this, id, name),
+ identifier: id
+ });
+ },
+ _onSubmitCallbackSuccess: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.SUBMITTED);
+
+ this._onSubmit(id, name);
+ this._options.callbacks.onSubmitted(id, name);
+
+ if (this._options.autoUpload) {
+ if (!this._handler.upload(id)) {
+ this._uploadData.setStatus(id, qq.status.QUEUED);
+ }
+ }
+ else {
+ this._storeForLater(id);
+ }
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _onValidateBatchCallbackSuccess: function(validationDescriptors, items, params, endpoint) {
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netUploadedOrQueued + validationDescriptors.length;
+
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ if (items.length > 0) {
+ this._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(this._options.callbacks.onValidate, this, items[0]),
+ onSuccess: qq.bind(this._onValidateCallbackSuccess, this, items, 0, params, endpoint),
+ onFailure: qq.bind(this._onValidateCallbackFailure, this, items, 0, params, endpoint),
+ identifier: "Item '" + items[0].name + "', size: " + items[0].size
+ });
+ }
+ else {
+ this._itemError("noFilesError", "");
+ }
+ }
+ else {
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ },
+ _onValidateCallbackSuccess: function(items, index, params, endpoint) {
+ var nextIndex = index+1,
+ validationDescriptor = this._getValidationDescriptor(items[index]),
+ validItem = false;
+
+ if (this._validateFileOrBlobData(items[index], validationDescriptor)) {
+ validItem = true;
+ this._upload(items[index], params, endpoint);
+ }
+
+ this._maybeProcessNextItemAfterOnValidateCallback(validItem, items, nextIndex, params, endpoint);
+ },
+ _onValidateCallbackFailure: function(items, index, params, endpoint) {
+ var nextIndex = index+ 1;
+
+ this._fileOrBlobRejected(undefined, items[0].name);
+
+ this._maybeProcessNextItemAfterOnValidateCallback(false, items, nextIndex, params, endpoint);
+ },
+ _maybeProcessNextItemAfterOnValidateCallback: function(validItem, items, index, params, endpoint) {
+ var self = this;
+
+ if (items.length > index) {
+ if (validItem || !this._options.validation.stopOnFirstInvalidFile) {
+ //use setTimeout to prevent a stack overflow with a large number of files in the batch & non-promissory callbacks
+ setTimeout(function() {
+ var validationDescriptor = self._getValidationDescriptor(items[index]);
+
+ self._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(self._options.callbacks.onValidate, self, items[index]),
+ onSuccess: qq.bind(self._onValidateCallbackSuccess, self, items, index, params, endpoint),
+ onFailure: qq.bind(self._onValidateCallbackFailure, self, items, index, params, endpoint),
+ identifier: "Item '" + validationDescriptor.name + "', size: " + validationDescriptor.size
+ });
+ }, 0);
+ }
+ }
+ },
+ _validateFileOrBlobData: function(item, validationDescriptor) {
+ var name = validationDescriptor.name,
+ size = validationDescriptor.size,
+ valid = true;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ valid = false;
+ }
+
+ if (qq.isFileOrInput(item) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ valid = false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ valid = false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ valid = false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ valid = false;
+ }
+
+ if (!valid) {
+ this._fileOrBlobRejected(undefined, name);
+ }
+
+ return valid;
+ },
+ _fileOrBlobRejected: function(id, name) {
+ if (id !== undefined) {
+ this._uploadData.setStatus(id, qq.status.REJECTED);
+ }
+ },
+ _itemError: function(code, nameOrNames) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ names = [].concat(nameOrNames),
+ name = names[0],
+ extensionsForMessage, placeholderMatch;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ placeholderMatch = message.match(/(\{\w+\})/g);
+ if (placeholderMatch !== null) {
+ qq.each(placeholderMatch, function(idx, placeholder) {
+ r(placeholder, names[idx]);
+ });
+ }
+
+ this._options.callbacks.onError(null, name, message, undefined);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message, undefined);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1000;
+ i++;
+ } while (bytes > 999);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size !== undefined) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ },
+ _handleCameraAccess: function() {
+ if (this._options.camera.ios && qq.ios()) {
+ this._options.multiple = false;
+
+ if (this._options.validation.acceptFiles === null) {
+ this._options.validation.acceptFiles = "image/*;capture=camera";
+ }
+ else {
+ this._options.validation.acceptFiles += ",image/*;capture=camera";
+ }
+ }
+ }
+};
+/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz,
+ droppedFiles = [],
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropZoneElements: [],
+ hideDropZonesBeforeEnter: false,
+ allowMultipleItems: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: new qq.DragAndDrop.callbacks()
+ };
+
+ qq.extend(options, o, true);
+
+ setupDragDrop();
+
+ function uploadDroppedFiles(files) {
+ options.callbacks.dropLog('Grabbed ' + files.length + " dropped files.");
+ dz.dropDisabled(false);
+ options.callbacks.processingDroppedFilesComplete(files);
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i,
+ parseEntryPromise = new qq.Promise();
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ droppedFiles.push(file);
+ parseEntryPromise.success();
+ },
+ function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+ else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ var entriesLeft = entries.length;
+
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]).done(function() {
+ entriesLeft-=1;
+
+ if (entriesLeft === 0) {
+ parseEntryPromise.success();
+ }
+ });
+ }
+
+ if (!entries.length) {
+ parseEntryPromise.success();
+ }
+ }, function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+
+ return parseEntryPromise;
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry,
+ pendingFolderPromises = [],
+ handleDataTransferPromise = new qq.Promise();
+
+ options.callbacks.processingDroppedFiles();
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.allowMultipleItems) {
+ options.callbacks.processingDroppedFilesComplete([]);
+ options.callbacks.dropError('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ handleDataTransferPromise.failure();
+ }
+ else {
+ droppedFiles = [];
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ }
+
+ else {
+ pendingFolderPromises.push(traverseFileTree(entry).done(function() {
+ pendingFolderPromises.pop();
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }));
+ }
+ }
+ }
+ }
+ else {
+ droppedFiles = dataTransfer.files;
+ }
+
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }
+
+ return handleDataTransferPromise;
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer).done(function() {
+ uploadDroppedFiles(droppedFiles);
+ });
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ var dropZones = options.dropZoneElements;
+
+ qq.each(dropZones, function(idx, dropZone) {
+ setupDropzone(dropZone);
+ })
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (dropZones.length && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).css({display: 'block'});
+ });
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropZonesBeforeEnter && qq.FineUploader.prototype._leaving_document_out(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setupExtraDropzone: function(element) {
+ options.dropZoneElements.push(element);
+ setupDropzone(element);
+ },
+
+ removeDropzone: function(element) {
+ var i,
+ dzs = options.dropZoneElements;
+
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+qq.DragAndDrop.callbacks = function() {
+ return {
+ processingDroppedFiles: function() {},
+ processingDroppedFilesComplete: function(files) {},
+ dropError: function(code, errorSpecifics) {
+ qq.log("Drag & drop error code '" + code + " with these specifics: '" + errorSpecifics + "'", "error");
+ },
+ dropLog: function(message, level) {
+ qq.log(message, level);
+ }
+ }
+}
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file",
+ unsupportedBrowser: "Unrecoverable error - this browser does not permit file uploading of any kind."
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false,
+ prependFiles: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+
+ if (!qq.supportedFeatures.uploading || (this._options.cors.expected && !qq.supportedFeatures.uploadCors)) {
+ this._options.element.innerHTML = "" + this._options.messages.unsupportedBrowser + "
"
+ }
+ else {
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._bindCancelAndRetryEvents();
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+ this._bindCancelAndRetryEvents();
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dropZoneElements = this._options.dragAndDrop.extraDropzones,
+ preventSelectFiles;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ dropZoneElements.push(this._find(this._options.element, 'drop'));
+ }
+
+ return new qq.DragAndDrop({
+ dropZoneElements: dropZoneElements,
+ hideDropZonesBeforeEnter: this._options.dragAndDrop.hideDropzones,
+ allowMultipleItems: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ processingDroppedFiles: function() {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ },
+ processingDroppedFilesComplete: function(files) {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ dropError: function(code, errorData) {
+ self._itemError(code, errorData);
+ },
+ dropLog: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type) {
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name) {
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ var onSuccessCallback = qq.bind(this._onSubmitDeleteSuccess, this, id);
+
+ qq.FineUploaderBasic.prototype._onSubmitDelete.call(this, id, onSuccessCallback);
+ },
+ _onSubmitDeleteSuccess: function(id) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ if (this._options.display.prependFiles) {
+ this._prependItem(item);
+ }
+ else {
+ this._listElement.appendChild(item);
+ }
+ this._filesInBatchAddedToUi += 1;
+
+ if (this._options.display.fileSizeOnSubmit && qq.supportedFeatures.ajaxUploading) {
+ this._displayFileSize(id);
+ }
+ },
+ _prependItem: function(item) {
+ var parentEl = this._listElement,
+ beforeEl = parentEl.firstChild;
+
+ if (this._totalFilesInBatch > 1 && this._filesInBatchAddedToUi > 0) {
+ beforeEl = qq(parentEl).children()[this._filesInBatchAddedToUi - 1].nextSibling;
+
+ }
+
+ parentEl.insertBefore(item, beforeEl);
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ /**
+ * delegate click event for cancel & retry links
+ **/
+ _bindCancelAndRetryEvents: function(){
+ var self = this,
+ list = this._listElement;
+
+ this._disposeSupport.attach(list, 'click', function(e){
+ e = e || window.event;
+ var target = e.target || e.srcElement;
+
+ if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
+ qq.preventDefault(e);
+
+ var item = target.parentNode;
+ while(item.qqFileId === undefined) {
+ item = item.parentNode;
+ }
+
+ if (qq(target).hasClass(self._classes.deleteButton)) {
+ self.deleteFile(item.qqFileId);
+ }
+ else if (qq(target).hasClass(self._classes.cancel)) {
+ self.cancel(item.qqFileId);
+ }
+ else {
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(item.qqFileId);
+ }
+ }
+ });
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ },
+ _fileOrBlobRejected: function(id, name) {
+ this._totalFilesInBatch -= 1;
+ qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this, arguments);
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ this._totalFilesInBatch = items.length;
+ this._filesInBatchAddedToUi = 0;
+ qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this, arguments);
+ }
+});
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+//TODO Use XDomainRequest if expectCors = true. Not necessary now since only DELETE requests are sent and XDR doesn't support pre-flighting.
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function(o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ successfulResponseCodes: [200],
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onSend: function(id) {},
+ onComplete: function(id, xhr, isError) {},
+ onCancel: function(id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = getMethod() === 'GET' || getMethod() === 'DELETE';
+
+
+ /**
+ * Removes element from queue, sends next request
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod(),
+ isError = false;
+
+ dequeue(id);
+
+ if (!isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function sendRequest(id) {
+ var xhr = new XMLHttpRequest(),
+ method = getMethod(),
+ params = {},
+ url;
+
+ options.onSend(id);
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ url = createUrl(id, params);
+
+ requestState[id].xhr = xhr;
+ xhr.onreadystatechange = getReadyStateChangeHandler(id);
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath !== undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ function getReadyStateChangeHandler(id) {
+ var xhr = requestState[id].xhr;
+
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function setHeaders(id) {
+ var xhr = requestState[id].xhr,
+ customHeaders = options.customHeaders;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ qq.each(customHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function cancelRequest(id) {
+ var xhr = requestState[id].xhr,
+ method = getMethod();
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes, responseCode) >= 0;
+ }
+
+ function getMethod() {
+ if (options.demoMode) {
+ return "GET";
+ }
+
+ return options.method;
+ }
+
+
+ return {
+ send: function(id, addToPath) {
+ requestState[id] = {
+ addToPath: addToPath
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections){
+ sendRequest(id);
+ }
+ },
+ cancel: function(id) {
+ return cancelRequest(id);
+ }
+ };
+};
+/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ options = {
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ requestor = new qq.AjaxRequestor({
+ method: 'DELETE',
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ successfulResponseCodes: [200, 202, 204],
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ requestor.send(id, uuid);
+ options.log("Submitted delete file request for " + id);
+ }
+ };
+};
+qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, handlerImpl, api;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ blobs: {
+ paramNames: {
+ name: 'qqblobname'
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){},
+ onUuidChanged: function(id, newUuid){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.supportedFeatures.ajaxUploading) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, options.onUuidChanged, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, options.onUuidChanged, log);
+ }
+
+ function cancelSuccess(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ dequeue(id);
+ }
+
+
+ api = {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ handlerImpl.upload(id);
+ return true;
+ }
+
+ return false;
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ var cancelRetVal = handlerImpl.cancel(id);
+
+ if (qq.isPromise(cancelRetVal)) {
+ cancelRetVal.then(function() {
+ cancelSuccess(id);
+ });
+ }
+ else if (cancelRetVal !== false) {
+ cancelSuccess(id);
+ }
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id){
+ return handlerImpl.getName(id);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ api.cancelAll();
+ queue = [];
+ handlerImpl.reset();
+ },
+ expunge: function(id) {
+ return handlerImpl.expunge(id);
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, onUuidChanged, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ formHandlerInstanceId = qq.getUniqueId(),
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var iframeName = iframe.id,
+ fileId = getFileIdForIframeName(iframeName);
+
+ onloadCallbacks[uuids[fileId]] = callback;
+
+ detachLoadEvents[fileId] = qq(iframe).attach('load', function() {
+ if (inputs[fileId]) {
+ log("Received iframe load event for CORS upload request (iframe name " + iframeName + ")");
+
+ postMessageCallbackTimers[iframeName] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for iframe name " + iframeName;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(iframeName, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = parseResponse(getFileIdForIframeName(iframeName), message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ log("Handling response for iframe name " + iframeName);
+ clearTimeout(postMessageCallbackTimers[iframeName]);
+ delete postMessageCallbackTimers[iframeName];
+
+ detachLoadEvent(iframeName);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(iframeName);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(id, iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHtml = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHtml);
+ //plain text response may be wrapped in tag
+ if (innerHtml && innerHtml.match(/^ ');
+
+ iframe.setAttribute('id', iframeName);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+ function expungeFile(id) {
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(getIframeName(id));
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ }
+
+ function getFileIdForIframeName(iframeName) {
+ return iframeName.split("_")[0];
+ }
+
+ function getIframeName(fileId) {
+ return fileId + "_" + formHandlerInstanceId;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ detachLoadEvents = {};
+ formHandlerInstanceId = qq.getUniqueId();
+ },
+ expunge: function(id) {
+ return expungeFile(id);
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, api.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeFile(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeFile(id);
+ return true;
+ }
+
+ return false;
+ },
+
+ upload: function(id) {
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, api.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(id, iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+ }
+ };
+
+ return api;
+};
+/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, onUuidChanged, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.supportedFeatures.chunking,
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.supportedFeatures.resume,
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.chunking.paramNames.filename] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.blobs.paramNames.name] = blobData.name;
+ }
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+ options.onComplete(id, name, response, xhr);
+
+ if (fileState[id]) {
+ delete fileState[id].xhr;
+ }
+
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(id, xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+
+ if (response.newUuid !== undefined) {
+ log("Server requested UUID change from '" + fileState[id].uuid + "' to '" + response.newUuid + "'");
+ fileState[id].uuid = response.newUuid;
+ onUuidChanged(id, response.newUuid);
+ }
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(id, xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex) {
+ var currentChunkIndex;
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+
+ function handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex) {
+ var name = api.getName(id),
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part),
+ onResumeRetVal;
+
+ onResumeRetVal = options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume));
+ if (qq.isPromise(onResumeRetVal)) {
+ log("Waiting for onResume promise to be fulfilled for " + id);
+ onResumeRetVal.then(
+ function() {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ },
+ function() {
+ log("onResume promise fulfilled - failure indicated. Will not resume.")
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ );
+ }
+ else if (onResumeRetVal !== false) {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ }
+ else {
+ log("onResume callback returned false. Will not resume.");
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var firstChunkIndex = 0,
+ persistedChunkInfoForResume;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex);
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ uploadNextChunk(id);
+ }
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+ function expungeItem(id) {
+ var xhr = fileState[id].xhr;
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ }
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id, persistedChunkData,
+ uuid = qq.getUniqueId();
+
+ if (qq.isFile(fileOrBlobData)) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (qq.isBlob(fileOrBlobData.blob)) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ if (resumeEnabled) {
+ persistedChunkData = getPersistedChunkData(id);
+
+ if (persistedChunkData) {
+ uuid = persistedChunkData.uuid;
+ }
+ }
+
+ fileState[id].uuid = uuid;
+
+ return id;
+ },
+ getName: function(id){
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData;
+
+ if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ getSize: function(id){
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ expunge: function(id) {
+ return expungeItem(id);
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry) {
+ var name = this.getName(id);
+
+ if (this.isValid(id)) {
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ }
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, this.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeItem(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeItem(id);
+ return true;
+ }
+
+ return false;
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
diff --git a/ajax/libs/file-uploader/3.6.4/fineuploader.min.css b/ajax/libs/file-uploader/3.6.4/fineuploader.min.css
new file mode 100644
index 000000000..110e8fa52
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.4/fineuploader.min.css
@@ -0,0 +1 @@
+.qq-uploader{position:relative;width:100%;}.qq-upload-button{display:block;width:105px;padding:7px 0;text-align:center;background:#800;border-bottom:1px solid #DDD;color:#FFF;}.qq-upload-button-hover{background:#C00;}.qq-upload-button-focus{outline:1px dotted #000;}.qq-upload-drop-area,.qq-upload-extra-drop-area{position:absolute;top:0;left:0;width:100%;height:100%;min-height:30px;z-index:2;background:#FF9797;text-align:center;}.qq-upload-drop-area span{display:block;position:absolute;top:50%;width:100%;margin-top:-8px;font-size:16px;}.qq-upload-extra-drop-area{position:relative;margin-top:50px;font-size:16px;padding-top:30px;height:20px;min-height:40px;}.qq-upload-drop-area-active{background:#FF7171;}.qq-upload-list{margin:0;padding:0;list-style:none;}.qq-upload-list li{margin:0;padding:9px;line-height:15px;font-size:16px;background-color:#FFF0BD;}.qq-upload-file,.qq-upload-spinner,.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-failed-text,.qq-upload-finished,.qq-upload-delete{margin-right:12px;}.qq-upload-spinner{display:inline-block;background:url("loading.gif");width:15px;height:15px;vertical-align:text-bottom;}.qq-drop-processing{display:none;}.qq-drop-processing-spinner{display:inline-block;background:url("processing.gif");width:24px;height:24px;vertical-align:text-bottom;}.qq-upload-finished{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-retry,.qq-upload-delete{display:none;color:#000;}.qq-upload-cancel,.qq-upload-delete{color:#000;}.qq-upload-retryable .qq-upload-retry{display:inline;}.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-delete{font-size:12px;font-weight:normal;}.qq-upload-failed-text{display:none;font-style:italic;font-weight:bold;}.qq-upload-failed-icon{display:none;width:15px;height:15px;vertical-align:text-bottom;}.qq-upload-fail .qq-upload-failed-text{display:inline;}.qq-upload-retrying .qq-upload-failed-text{display:inline;color:#D60000;}.qq-upload-list li.qq-upload-success{background-color:#5DA30C;color:#FFF;}.qq-upload-list li.qq-upload-fail{background-color:#D60000;color:#FFF;}.qq-progress-bar{background:-moz-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,rgba(30,87,153,1)),color-stop(50%,rgba(41,137,216,1)),color-stop(51%,rgba(32,124,202,1)),color-stop(100%,rgba(125,185,232,1)));background:-webkit-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-o-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-ms-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:linear-gradient(to bottom,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);width:0;height:15px;border-radius:6px;margin-bottom:3px;display:none;}
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.4/fineuploader.min.js b/ajax/libs/file-uploader/3.6.4/fineuploader.min.js
new file mode 100644
index 000000000..4106f1dfc
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.4/fineuploader.min.js
@@ -0,0 +1,15 @@
+/**
+ * http://github.com/Widen/fine-uploader
+ *
+ * Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
+ *
+ * Copyright © 2013, Widen Enterprises info@fineupoader.com
+ *
+ * Version: 3.6.4
+ *
+ * Licensed under GNU GPL v3, see license.txt.
+ */
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq=function(element){"use strict";return{hide:function(){element.style.display="none";return this},attach:function(type,fn){if(element.addEventListener){element.addEventListener(type,fn,false)}else if(element.attachEvent){element.attachEvent("on"+type,fn)}return function(){qq(element).detach(type,fn)}},detach:function(type,fn){if(element.removeEventListener){element.removeEventListener(type,fn,false)}else if(element.attachEvent){element.detachEvent("on"+type,fn)}return this},contains:function(descendant){if(element===descendant){return true}if(element.contains){return element.contains(descendant)}else{return!!(descendant.compareDocumentPosition(element)&8)}},insertBefore:function(elementB){elementB.parentNode.insertBefore(element,elementB);return this},remove:function(){element.parentNode.removeChild(element);return this},css:function(styles){if(styles.opacity!=null){if(typeof element.style.opacity!=="string"&&typeof element.filters!=="undefined"){styles.filter="alpha(opacity="+Math.round(100*styles.opacity)+")"}}qq.extend(element.style,styles);return this},hasClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");return re.test(element.className)},addClass:function(name){if(!qq(element).hasClass(name)){element.className+=" "+name}return this},removeClass:function(name){var re=new RegExp("(^| )"+name+"( |$)");element.className=element.className.replace(re," ").replace(/^\s+|\s+$/g,"");return this},getByClass:function(className){var candidates,result=[];if(element.querySelectorAll){return element.querySelectorAll("."+className)}candidates=element.getElementsByTagName("*");qq.each(candidates,function(idx,val){if(qq(val).hasClass(className)){result.push(val)}});return result},children:function(){var children=[],child=element.firstChild;while(child){if(child.nodeType===1){children.push(child)}child=child.nextSibling}return children},setText:function(text){element.innerText=text;element.textContent=text;return this},clearText:function(){return qq(element).setText("")}}};qq.log=function(message,level){"use strict";if(window.console){if(!level||level==="info"){window.console.log(message)}else{if(window.console[level]){window.console[level](message)}else{window.console.log("<"+level+"> "+message)}}}};qq.isObject=function(variable){"use strict";return variable&&!variable.nodeType&&Object.prototype.toString.call(variable)==="[object Object]"};qq.isFunction=function(variable){"use strict";return typeof variable==="function"};qq.isArray=function(variable){"use strict";return Object.prototype.toString.call(variable)==="[object Array]"};qq.isString=function(maybeString){"use strict";return Object.prototype.toString.call(maybeString)==="[object String]"};qq.trimStr=function(string){if(String.prototype.trim){return string.trim()}return string.replace(/^\s+|\s+$/g,"")};qq.isFile=function(maybeFile){"use strict";return window.File&&Object.prototype.toString.call(maybeFile)==="[object File]"};qq.isFileList=function(maybeFileList){return window.FileList&&Object.prototype.toString.call(maybeFileList)==="[object FileList]"};qq.isFileOrInput=function(maybeFileOrInput){"use strict";return qq.isFile(maybeFileOrInput)||qq.isInput(maybeFileOrInput)};qq.isInput=function(maybeInput){if(window.HTMLInputElement){if(Object.prototype.toString.call(maybeInput)==="[object HTMLInputElement]"){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}if(maybeInput.tagName){if(maybeInput.tagName.toLowerCase()==="input"){if(maybeInput.type&&maybeInput.type.toLowerCase()==="file"){return true}}}return false};qq.isBlob=function(maybeBlob){"use strict";return window.Blob&&Object.prototype.toString.call(maybeBlob)==="[object Blob]"};qq.isXhrUploadSupported=function(){"use strict";var input=document.createElement("input");input.type="file";return input.multiple!==undefined&&typeof File!=="undefined"&&typeof FormData!=="undefined"&&typeof(new XMLHttpRequest).upload!=="undefined"};qq.isFolderDropSupported=function(dataTransfer){"use strict";return dataTransfer.items&&dataTransfer.items[0].webkitGetAsEntry};qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(File.prototype.slice!==undefined||File.prototype.webkitSlice!==undefined||File.prototype.mozSlice!==undefined)};qq.extend=function(first,second,extendNested){"use strict";qq.each(second,function(prop,val){if(extendNested&&qq.isObject(val)){if(first[prop]===undefined){first[prop]={}}qq.extend(first[prop],val,true)}else{first[prop]=val}});return first};qq.indexOf=function(arr,elt,from){"use strict";if(arr.indexOf){return arr.indexOf(elt,from)}from=from||0;var len=arr.length;if(from<0){from+=len}for(;from33){fileOrBlobName=fileOrBlobName.slice(0,19)+"..."+fileOrBlobName.slice(-14)}return fileOrBlobName},text:{defaultResponseError:"Upload failure reason unknown",sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:false,endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:false,sendCredentials:false},blobs:{defaultName:"misc_data",paramNames:{name:"qqblobname"}},paste:{targetElement:null,defaultName:"pasted_image"},camera:{ios:false}};qq.extend(this._options,o,true);this._handleCameraAccess();this._wrapCallbacks();this._disposeSupport=new qq.DisposeSupport;this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData=this._createUploadDataTracker();this._paramsStore=this._createParamsStore("request");this._deleteFileParamsStore=this._createParamsStore("deleteFile");this._endpointStore=this._createEndpointStore("request");this._deleteFileEndpointStore=this._createEndpointStore("deleteFile");this._handler=this._createUploadHandler();this._deleteHandler=this._createDeleteHandler();if(this._options.button){this._button=this._createUploadButton(this._options.button)}if(this._options.paste.targetElement){this._pasteHandler=this._createPasteHandler()}this._preventLeaveInProgress()};qq.FineUploaderBasic.prototype={log:function(str,level){if(this._options.debug&&(!level||level==="info")){qq.log("[FineUploader "+qq.version+"] "+str)}else if(level&&level!=="info"){qq.log("[FineUploader "+qq.version+"] "+str,level)}},setParams:function(params,id){if(id==null){this._options.request.params=params}else{this._paramsStore.setParams(params,id)}},setDeleteFileParams:function(params,id){if(id==null){this._options.deleteFile.params=params}else{this._deleteFileParamsStore.setParams(params,id)}},setEndpoint:function(endpoint,id){if(id==null){this._options.request.endpoint=endpoint}else{this._endpointStore.setEndpoint(endpoint,id)}},getInProgress:function(){return this._filesInProgress.length},getNetUploads:function(){return this._netUploaded},uploadStoredFiles:function(){"use strict";var idToUpload;while(this._storedIds.length){idToUpload=this._storedIds.shift();this._filesInProgress.push(idToUpload);this._handler.upload(idToUpload)}},clearStoredFiles:function(){this._storedIds=[]},retry:function(id){if(this._onBeforeManualRetry(id)){this._netUploadedOrQueued++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id);return true}else{return false}},cancel:function(id){this._handler.cancel(id)},cancelAll:function(){var storedIdsCopy=[],self=this;qq.extend(storedIdsCopy,this._storedIds);qq.each(storedIdsCopy,function(idx,storedFileId){self.cancel(storedFileId)});this._handler.cancelAll()},reset:function(){this.log("Resetting uploader...");this._handler.reset();this._filesInProgress=[];this._storedIds=[];this._autoRetries=[];this._retryTimeouts=[];this._preventRetries=[];this._button.reset();this._paramsStore.reset();this._endpointStore.reset();this._netUploadedOrQueued=0;this._netUploaded=0;this._uploadData.reset();if(this._pasteHandler){this._pasteHandler.reset()}},addFiles:function(filesOrInputs,params,endpoint){var self=this,verifiedFilesOrInputs=[],fileOrInputIndex,fileOrInput,fileIndex;if(filesOrInputs){if(!qq.isFileList(filesOrInputs)){filesOrInputs=[].concat(filesOrInputs)}for(fileOrInputIndex=0;fileOrInputIndex=0){this._storedIds.splice(storedItemIndex,1)}},_isDeletePossible:function(){return this._options.deleteFile.enabled&&(!this._options.cors.expected||qq.supportedFeatures.deleteFileCors)},_onSubmitDelete:function(id,onSuccessCallback){if(this._isDeletePossible()){return this._handleCheckedCallback({name:"onSubmitDelete",callback:qq.bind(this._options.callbacks.onSubmitDelete,this,id),onSuccess:onSuccessCallback||qq.bind(this._deleteHandler.sendDelete,this,id,this.getUuid(id)),identifier:id})}else{this.log("Delete request ignored for ID "+id+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn");return false}},_onDelete:function(id){this._uploadData.setStatus(id,qq.status.DELETING)},_onDeleteComplete:function(id,xhr,isError){var name=this._handler.getName(id);
+if(isError){this._uploadData.setStatus(id,qq.status.DELETE_FAILED);this.log("Delete request for '"+name+"' has failed.","error");this._options.callbacks.onError(id,name,"Delete request failed with response code "+xhr.status,xhr)}else{this._uploadData.setStatus(id,qq.status.DELETED);this._netUploadedOrQueued--;this._netUploaded--;this._handler.expunge(id);this.log("Delete request for '"+name+"' has succeeded.")}},_removeFromFilesInProgress:function(id){var index=qq.indexOf(this._filesInProgress,id);if(index>=0){this._filesInProgress.splice(index,1)}},_onUpload:function(id,name){this._uploadData.setStatus(id,qq.status.UPLOADING)},_onInputChange:function(input){if(qq.supportedFeatures.ajaxUploading){this.addFiles(input.files)}else{this.addFiles(input)}this._button.reset()},_onBeforeAutoRetry:function(id,name){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+name+"...")},_onAutoRetry:function(id,name,responseJSON){this.log("Retrying "+name+"...");this._autoRetries[id]++;this._uploadData.setStatus(id,qq.status.UPLOAD_RETRYING);this._handler.retry(id)},_shouldAutoRetry:function(id,name,responseJSON){if(!this._preventRetries[id]&&this._options.retry.enableAuto){if(this._autoRetries[id]===undefined){this._autoRetries[id]=0}return this._autoRetries[id]0&&this._netUploadedOrQueued+1>itemLimit){this._itemError("retryFailTooManyItems","");return false}this.log("Retrying upload for '"+fileName+"' (id: "+id+")...");this._filesInProgress.push(id);return true}else{this.log("'"+id+"' is not a valid file ID","error");return false}},_maybeParseAndSendUploadError:function(id,name,response,xhr){if(!response.success){if(xhr&&xhr.status!==200&&!response.error){this._options.callbacks.onError(id,name,"XHR returned response code "+xhr.status,xhr)}else{var errorReason=response.error?response.error:this._options.text.defaultResponseError;this._options.callbacks.onError(id,name,errorReason,xhr)}}},_prepareItemsForUpload:function(items,params,endpoint){var validationDescriptors=this._getValidationDescriptors(items);this._handleCheckedCallback({name:"onValidateBatch",callback:qq.bind(this._options.callbacks.onValidateBatch,this,validationDescriptors),onSuccess:qq.bind(this._onValidateBatchCallbackSuccess,this,validationDescriptors,items,params,endpoint),identifier:"batch validation"})},_upload:function(blobOrFileContainer,params,endpoint){var id=this._handler.add(blobOrFileContainer),name=this._handler.getName(id);this._uploadData.added(id);if(params){this.setParams(params,id)}if(endpoint){this.setEndpoint(endpoint,id)}this._handleCheckedCallback({name:"onSubmit",callback:qq.bind(this._options.callbacks.onSubmit,this,id,name),onSuccess:qq.bind(this._onSubmitCallbackSuccess,this,id,name),onFailure:qq.bind(this._fileOrBlobRejected,this,id,name),identifier:id})},_onSubmitCallbackSuccess:function(id,name){this._uploadData.setStatus(id,qq.status.SUBMITTED);this._onSubmit(id,name);this._options.callbacks.onSubmitted(id,name);if(this._options.autoUpload){if(!this._handler.upload(id)){this._uploadData.setStatus(id,qq.status.QUEUED)}}else{this._storeForLater(id)}},_storeForLater:function(id){this._storedIds.push(id)},_onValidateBatchCallbackSuccess:function(validationDescriptors,items,params,endpoint){var errorMessage,itemLimit=this._options.validation.itemLimit,proposedNetFilesUploadedOrQueued=this._netUploadedOrQueued+validationDescriptors.length;if(itemLimit===0||proposedNetFilesUploadedOrQueued<=itemLimit){if(items.length>0){this._handleCheckedCallback({name:"onValidate",callback:qq.bind(this._options.callbacks.onValidate,this,items[0]),onSuccess:qq.bind(this._onValidateCallbackSuccess,this,items,0,params,endpoint),onFailure:qq.bind(this._onValidateCallbackFailure,this,items,0,params,endpoint),identifier:"Item '"+items[0].name+"', size: "+items[0].size})}else{this._itemError("noFilesError","")}}else{errorMessage=this._options.messages.tooManyItemsError.replace(/\{netItems\}/g,proposedNetFilesUploadedOrQueued).replace(/\{itemLimit\}/g,itemLimit);this._batchError(errorMessage)}},_onValidateCallbackSuccess:function(items,index,params,endpoint){var nextIndex=index+1,validationDescriptor=this._getValidationDescriptor(items[index]),validItem=false;if(this._validateFileOrBlobData(items[index],validationDescriptor)){validItem=true;this._upload(items[index],params,endpoint)}this._maybeProcessNextItemAfterOnValidateCallback(validItem,items,nextIndex,params,endpoint)},_onValidateCallbackFailure:function(items,index,params,endpoint){var nextIndex=index+1;this._fileOrBlobRejected(undefined,items[0].name);this._maybeProcessNextItemAfterOnValidateCallback(false,items,nextIndex,params,endpoint)},_maybeProcessNextItemAfterOnValidateCallback:function(validItem,items,index,params,endpoint){var self=this;if(items.length>index){if(validItem||!this._options.validation.stopOnFirstInvalidFile){setTimeout(function(){var validationDescriptor=self._getValidationDescriptor(items[index]);self._handleCheckedCallback({name:"onValidate",callback:qq.bind(self._options.callbacks.onValidate,self,items[index]),onSuccess:qq.bind(self._onValidateCallbackSuccess,self,items,index,params,endpoint),onFailure:qq.bind(self._onValidateCallbackFailure,self,items,index,params,endpoint),identifier:"Item '"+validationDescriptor.name+"', size: "+validationDescriptor.size})},0)}}},_validateFileOrBlobData:function(item,validationDescriptor){var name=validationDescriptor.name,size=validationDescriptor.size,valid=true;if(this._options.callbacks.onValidate(validationDescriptor)===false){valid=false}if(qq.isFileOrInput(item)&&!this._isAllowedExtension(name)){this._itemError("typeError",name);valid=false}else if(size===0){this._itemError("emptyError",name);valid=false}else if(size&&this._options.validation.sizeLimit&&size>this._options.validation.sizeLimit){this._itemError("sizeError",name);valid=false}else if(size&&size999);return Math.max(bytes,.1).toFixed(1)+this._options.text.sizeSymbols[i]},_wrapCallbacks:function(){var self,safeCallback;self=this;safeCallback=function(name,callback,args){try{return callback.apply(self,args)}catch(exception){self.log("Caught exception in '"+name+"' callback - "+exception.message,"error")}};for(var prop in this._options.callbacks){!function(){var callbackName,callbackFunc;callbackName=prop;callbackFunc=self._options.callbacks[callbackName];self._options.callbacks[callbackName]=function(){return safeCallback(callbackName,callbackFunc,arguments)}}()}},_parseFileOrBlobDataName:function(fileOrBlobData){var name;if(qq.isFileOrInput(fileOrBlobData)){if(fileOrBlobData.value){name=fileOrBlobData.value.replace(/.*(\/|\\)/,"")}else{name=fileOrBlobData.fileName!==null&&fileOrBlobData.fileName!==undefined?fileOrBlobData.fileName:fileOrBlobData.name}}else{name=fileOrBlobData.name}return name},_parseFileOrBlobDataSize:function(fileOrBlobData){var size;if(qq.isFileOrInput(fileOrBlobData)){if(!fileOrBlobData.value){size=fileOrBlobData.fileSize!==null&&fileOrBlobData.fileSize!==undefined?fileOrBlobData.fileSize:fileOrBlobData.size}}else{size=fileOrBlobData.blob.size}return size},_getValidationDescriptor:function(fileOrBlobData){var name,size,fileDescriptor;fileDescriptor={};name=this._parseFileOrBlobDataName(fileOrBlobData);size=this._parseFileOrBlobDataSize(fileOrBlobData);fileDescriptor.name=name;if(size!==undefined){fileDescriptor.size=size}return fileDescriptor},_getValidationDescriptors:function(files){var self=this,fileDescriptors=[];qq.each(files,function(idx,file){fileDescriptors.push(self._getValidationDescriptor(file))});return fileDescriptors},_createParamsStore:function(type){var paramsStore={},self=this;return{setParams:function(params,id){var paramsCopy={};qq.extend(paramsCopy,params);paramsStore[id]=paramsCopy},getParams:function(id){var paramsCopy={};if(id!=null&¶msStore[id]){qq.extend(paramsCopy,paramsStore[id])}else{qq.extend(paramsCopy,self._options[type].params)}return paramsCopy},remove:function(fileId){return delete paramsStore[fileId]},reset:function(){paramsStore={}}}},_createEndpointStore:function(type){var endpointStore={},self=this;return{setEndpoint:function(endpoint,id){endpointStore[id]=endpoint},getEndpoint:function(id){if(id!=null&&endpointStore[id]){return endpointStore[id]}return self._options[type].endpoint},remove:function(fileId){return delete endpointStore[fileId]},reset:function(){endpointStore={}}}},_handleCameraAccess:function(){if(this._options.camera.ios&&qq.ios()){this._options.multiple=false;if(this._options.validation.acceptFiles===null){this._options.validation.acceptFiles="image/*;capture=camera"}else{this._options.validation.acceptFiles+=",image/*;capture=camera"}}}};qq.DragAndDrop=function(o){"use strict";var options,dz,droppedFiles=[],disposeSupport=new qq.DisposeSupport;options={dropZoneElements:[],hideDropZonesBeforeEnter:false,allowMultipleItems:true,classes:{dropActive:null},callbacks:new qq.DragAndDrop.callbacks};qq.extend(options,o,true);setupDragDrop();function uploadDroppedFiles(files){options.callbacks.dropLog("Grabbed "+files.length+" dropped files.");dz.dropDisabled(false);options.callbacks.processingDroppedFilesComplete(files)}function traverseFileTree(entry){var dirReader,i,parseEntryPromise=new qq.Promise;if(entry.isFile){entry.file(function(file){droppedFiles.push(file);parseEntryPromise.success()},function(fileError){options.callbacks.dropLog("Problem parsing '"+entry.fullPath+"'. FileError code "+fileError.code+".","error");parseEntryPromise.failure()})}else if(entry.isDirectory){dirReader=entry.createReader();dirReader.readEntries(function(entries){var entriesLeft=entries.length;for(i=0;i1&&!options.allowMultipleItems){options.callbacks.processingDroppedFilesComplete([]);options.callbacks.dropError("tooManyFilesError","");dz.dropDisabled(false);handleDataTransferPromise.failure()}else{droppedFiles=[];if(qq.isFolderDropSupported(dataTransfer)){items=dataTransfer.items;for(i=0;i'+(!this._options.dragAndDrop||!this._options.dragAndDrop.disableDefaultDropzone?'{dragZoneText}
':"")+(!this._options.button?'':"")+'{dropProcessingText} '+(!this._options.listElement?'':"")+"",fileTemplate:""+'
'+' '+' '+' '+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:true},messages:{tooManyFilesError:"You may only drop one file",unsupportedBrowser:"Unrecoverable error - this browser does not permit file uploading of any kind."},retry:{showAutoRetryNote:true,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:false},deleteFile:{forceConfirm:false,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:false,prependFiles:false},paste:{promptForName:false,namePromptMessage:"Please name this image"},showMessage:function(message){setTimeout(function(){window.alert(message)},0)},showConfirm:function(message,okCallback,cancelCallback){setTimeout(function(){var result=window.confirm(message);if(result){okCallback()}else if(cancelCallback){cancelCallback()}},0)},showPrompt:function(message,defaultValue){var promise=new qq.Promise,retVal=window.prompt(message,defaultValue);if(retVal!=null&&qq.trimStr(retVal).length>0){promise.success(retVal)}else{promise.failure("Undefined or invalid user-supplied value.")}return promise}},true);qq.extend(this._options,o,true);if(!qq.supportedFeatures.uploading||this._options.cors.expected&&!qq.supportedFeatures.uploadCors){this._options.element.innerHTML=""+this._options.messages.unsupportedBrowser+"
"}else{this._wrapCallbacks();this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone);this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton);this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing);this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton);this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,"");this._element=this._options.element;this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");this._classes=this._options.classes;if(!this._button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd=this._setupDragAndDrop();if(this._options.paste.targetElement&&this._options.paste.promptForName){this._setupPastePrompt()}this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0}};qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype);qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments);this._listElement.innerHTML=""},addExtraDropzone:function(element){this._dnd.setupExtraDropzone(element)},removeExtraDropzone:function(element){return this._dnd.removeDropzone(element)},getItemByFileId:function(id){var item=this._listElement.firstChild;while(item){if(item.qqFileId==id)return item;item=item.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments);this._element.innerHTML=this._options.template;this._listElement=this._options.listElement||this._find(this._element,"list");if(!this._options.button){this._button=this._createUploadButton(this._find(this._element,"button"))}this._bindCancelAndRetryEvents();this._dnd.dispose();this._dnd=this._setupDragAndDrop();this._totalFilesInBatch=0;this._filesInBatchAddedToUi=0},_removeFileItem:function(fileId){var item=this.getItemByFileId(fileId);qq(item).remove()},_setupDragAndDrop:function(){var self=this,dropProcessingEl=this._find(this._element,"dropProcessing"),dropZoneElements=this._options.dragAndDrop.extraDropzones,preventSelectFiles;preventSelectFiles=function(event){event.preventDefault()};if(!this._options.dragAndDrop.disableDefaultDropzone){dropZoneElements.push(this._find(this._options.element,"drop"))}return new qq.DragAndDrop({dropZoneElements:dropZoneElements,hideDropZonesBeforeEnter:this._options.dragAndDrop.hideDropzones,allowMultipleItems:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{processingDroppedFiles:function(){var input=self._button.getInput();qq(dropProcessingEl).css({display:"block"});qq(input).attach("click",preventSelectFiles)},processingDroppedFilesComplete:function(files){var input=self._button.getInput();qq(dropProcessingEl).hide();qq(input).detach("click",preventSelectFiles);if(files){self.addFiles(files)}},dropError:function(code,errorData){self._itemError(code,errorData)},dropLog:function(message,level){self.log(message,level)}}})},_leaving_document_out:function(e){return(qq.chrome()||qq.safari()&&qq.windows())&&e.clientX==0&&e.clientY==0||qq.firefox()&&!e.relatedTarget},_storeForLater:function(id){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"spinner")).hide()},_find:function(parent,type){var element=qq(parent).getByClass(this._options.classes[type])[0];if(!element){throw new Error("element not found "+type)}return element},_onSubmit:function(id,name){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments);this._addToList(id,name)},_onProgress:function(id,name,loaded,total){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var item,progressBar,percent,cancelLink;item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");percent=Math.round(loaded/total*100);if(loaded===total){cancelLink=this._find(item,"cancel");qq(cancelLink).hide();qq(progressBar).hide();qq(this._find(item,"statusText")).setText(this._options.text.waitingForResponse);this._displayFileSize(id)}else{this._displayFileSize(id,loaded,total);qq(progressBar).css({display:"block"})}qq(progressBar).css({width:percent+"%"})},_onComplete:function(id,name,result,xhr){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var item=this.getItemByFileId(id);qq(this._find(item,"statusText")).clearText();qq(item).removeClass(this._classes.retrying);qq(this._find(item,"progressBar")).hide();if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){qq(this._find(item,"cancel")).hide()}qq(this._find(item,"spinner")).hide();if(result.success){if(this._isDeletePossible()){this._showDeleteLink(id)}qq(item).addClass(this._classes.success);if(this._classes.successIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.successIcon)}}else{qq(item).addClass(this._classes.fail);if(this._classes.failIcon){this._find(item,"finished").style.display="inline-block";qq(item).addClass(this._classes.failIcon)}if(this._options.retry.showButton&&!this._preventRetries[id]){qq(item).addClass(this._classes.retryable)}this._controlFailureTextDisplay(item,result)}},_onUpload:function(id,name){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments);this._showSpinner(id)},_onCancel:function(id,name){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments);this._removeFileItem(id)},_onBeforeAutoRetry:function(id){var item,progressBar,failTextEl,retryNumForDisplay,maxAuto,retryNote;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments);item=this.getItemByFileId(id);progressBar=this._find(item,"progressBar");this._showCancelLink(item);progressBar.style.width=0;qq(progressBar).hide();if(this._options.retry.showAutoRetryNote){failTextEl=this._find(item,"statusText");retryNumForDisplay=this._autoRetries[id]+1;maxAuto=this._options.retry.maxAutoAttempts;retryNote=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,retryNumForDisplay);retryNote=retryNote.replace(/\{maxAuto\}/g,maxAuto);qq(failTextEl).setText(retryNote);if(retryNumForDisplay===1){qq(item).addClass(this._classes.retrying)}}},_onBeforeManualRetry:function(id){var item=this.getItemByFileId(id);if(qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)){this._find(item,"progressBar").style.width=0;qq(item).removeClass(this._classes.fail);qq(this._find(item,"statusText")).clearText();this._showSpinner(id);this._showCancelLink(item);return true}else{qq(item).addClass(this._classes.retryable);return false}},_onSubmitDelete:function(id){var onSuccessCallback=qq.bind(this._onSubmitDeleteSuccess,this,id);qq.FineUploaderBasic.prototype._onSubmitDelete.call(this,id,onSuccessCallback)},_onSubmitDeleteSuccess:function(id){if(this._options.deleteFile.forceConfirm){this._showDeleteConfirm(id)}else{this._sendDeleteRequest(id)}},_onDeleteComplete:function(id,xhr,isError){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner"),statusTextEl=this._find(item,"statusText");qq(spinnerEl).hide();if(isError){qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);this._showDeleteLink(id)}else{this._removeFileItem(id)}},_sendDeleteRequest:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton"),statusTextEl=this._find(item,"statusText");qq(deleteLink).hide();this._showSpinner(id);qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);this._deleteHandler.sendDelete(id,this.getUuid(id))},_showDeleteConfirm:function(id){var fileName=this._handler.getName(id),confirmMessage=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,fileName),uuid=this.getUuid(id),self=this;this._options.showConfirm(confirmMessage,function(){self._sendDeleteRequest(id)})},_addToList:function(id,name){var item=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).remove()}item.qqFileId=id;var fileElement=this._find(item,"file");qq(fileElement).setText(this._options.formatFileName(name));qq(this._find(item,"size")).hide();if(!this._options.multiple){this._handler.cancelAll();this._clearList()}if(this._options.display.prependFiles){this._prependItem(item)}else{this._listElement.appendChild(item)}this._filesInBatchAddedToUi+=1;if(this._options.display.fileSizeOnSubmit&&qq.supportedFeatures.ajaxUploading){this._displayFileSize(id)}},_prependItem:function(item){var parentEl=this._listElement,beforeEl=parentEl.firstChild;if(this._totalFilesInBatch>1&&this._filesInBatchAddedToUi>0){beforeEl=qq(parentEl).children()[this._filesInBatchAddedToUi-1].nextSibling}parentEl.insertBefore(item,beforeEl)},_clearList:function(){this._listElement.innerHTML="";this.clearStoredFiles()},_displayFileSize:function(id,loadedSize,totalSize){var item=this.getItemByFileId(id),size=this.getSize(id),sizeForDisplay=this._formatSize(size),sizeEl=this._find(item,"size");if(loadedSize!==undefined&&totalSize!==undefined){sizeForDisplay=this._formatProgress(loadedSize,totalSize)}qq(sizeEl).css({display:"inline"});qq(sizeEl).setText(sizeForDisplay)},_bindCancelAndRetryEvents:function(){var self=this,list=this._listElement;this._disposeSupport.attach(list,"click",function(e){e=e||window.event;var target=e.target||e.srcElement;if(qq(target).hasClass(self._classes.cancel)||qq(target).hasClass(self._classes.retry)||qq(target).hasClass(self._classes.deleteButton)){qq.preventDefault(e);var item=target.parentNode;while(item.qqFileId===undefined){item=item.parentNode}if(qq(target).hasClass(self._classes.deleteButton)){self.deleteFile(item.qqFileId)}else if(qq(target).hasClass(self._classes.cancel)){self.cancel(item.qqFileId)}else{qq(item).removeClass(self._classes.retryable);self.retry(item.qqFileId)}}})},_formatProgress:function(uploadedSize,totalSize){var message=this._options.text.formatProgress;function r(name,replacement){message=message.replace(name,replacement)}r("{percent}",Math.round(uploadedSize/totalSize*100));r("{total_size}",this._formatSize(totalSize));return message},_controlFailureTextDisplay:function(item,response){var mode,maxChars,responseProperty,failureReason,shortFailureReason;mode=this._options.failedUploadTextDisplay.mode;maxChars=this._options.failedUploadTextDisplay.maxChars;responseProperty=this._options.failedUploadTextDisplay.responseProperty;if(mode==="custom"){failureReason=response[responseProperty];if(failureReason){if(failureReason.length>maxChars){shortFailureReason=failureReason.substring(0,maxChars)+"..."}}else{failureReason=this._options.text.failUpload;this.log("'"+responseProperty+"' is not a valid property on the server response.","warn")}qq(this._find(item,"statusText")).setText(shortFailureReason||failureReason);if(this._options.failedUploadTextDisplay.enableTooltip){this._showTooltip(item,failureReason)
+}}else if(mode==="default"){qq(this._find(item,"statusText")).setText(this._options.text.failUpload)}else if(mode!=="none"){this.log("failedUploadTextDisplay.mode value of '"+mode+"' is not valid","warn")}},_showTooltip:function(item,text){item.title=text},_showSpinner:function(id){var item=this.getItemByFileId(id),spinnerEl=this._find(item,"spinner");spinnerEl.style.display="inline-block"},_showCancelLink:function(item){if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){var cancelLink=this._find(item,"cancel");qq(cancelLink).css({display:"inline"})}},_showDeleteLink:function(id){var item=this.getItemByFileId(id),deleteLink=this._find(item,"deleteButton");qq(deleteLink).css({display:"inline"})},_itemError:function(code,name){var message=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(message)},_batchError:function(message){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments);this._options.showMessage(message)},_setupPastePrompt:function(){var self=this;this._options.callbacks.onPasteReceived=function(){var message=self._options.paste.namePromptMessage,defaultVal=self._options.paste.defaultName;return self._options.showPrompt(message,defaultVal)}},_fileOrBlobRejected:function(id,name){this._totalFilesInBatch-=1;qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this,arguments)},_prepareItemsForUpload:function(items,params,endpoint){this._totalFilesInBatch=items.length;this._filesInBatchAddedToUi=0;qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this,arguments)}});qq.AjaxRequestor=function(o){"use strict";var log,shouldParamsBeInQueryString,queue=[],requestState=[],options={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},successfulResponseCodes:[200],demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onSend:function(id){},onComplete:function(id,xhr,isError){},onCancel:function(id){}};qq.extend(options,o);log=options.log;shouldParamsBeInQueryString=getMethod()==="GET"||getMethod()==="DELETE";function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;delete requestState[id];queue.splice(i,1);if(queue.length>=max&&i=0}function getMethod(){if(options.demoMode){return"GET"}return options.method}return{send:function(id,addToPath){requestState[id]={addToPath:addToPath};var len=queue.push(id);if(len<=options.maxConnections){sendRequest(id)}},cancel:function(id){return cancelRequest(id)}}};qq.DeleteFileAjaxRequestor=function(o){"use strict";var requestor,options={endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:false,cors:{expected:false,sendCredentials:false},log:function(str,level){},onDelete:function(id){},onDeleteComplete:function(id,xhr,isError){}};qq.extend(options,o);requestor=new qq.AjaxRequestor({method:"DELETE",endpointStore:options.endpointStore,paramsStore:options.paramsStore,maxConnections:options.maxConnections,customHeaders:options.customHeaders,successfulResponseCodes:[200,202,204],demoMode:options.demoMode,log:options.log,onSend:options.onDelete,onComplete:options.onDeleteComplete});return{sendDelete:function(id,uuid){requestor.send(id,uuid);options.log("Submitted delete file request for "+id)}}};qq.WindowReceiveMessage=function(o){var options={log:function(message,level){}},callbackWrapperDetachers={};qq.extend(options,o);return{receiveMessage:function(id,callback){var onMessageCallbackWrapper=function(event){callback(event.data)};if(window.postMessage){callbackWrapperDetachers[id]=qq(window).attach("message",onMessageCallbackWrapper)}else{log("iframe message passing not supported in this browser!","error")}},stopReceivingMessages:function(id){if(window.postMessage){var detacher=callbackWrapperDetachers[id];if(detacher){detacher()}}}}};qq.UploadHandler=function(o){"use strict";var queue=[],options,log,handlerImpl,api;options={debug:false,forceMultipart:true,paramsInBody:false,paramsStore:{},endpointStore:{},cors:{expected:false,sendCredentials:false},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:false,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:false,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},blobs:{paramNames:{name:"qqblobname"}},log:function(str,level){},onProgress:function(id,fileName,loaded,total){},onComplete:function(id,fileName,response,xhr){},onCancel:function(id,fileName){},onUpload:function(id,fileName){},onUploadChunk:function(id,fileName,chunkData){},onAutoRetry:function(id,fileName,response,xhr){},onResume:function(id,fileName,chunkData){},onUuidChanged:function(id,newUuid){}};qq.extend(options,o);log=options.log;function dequeue(id){var i=qq.indexOf(queue,id),max=options.maxConnections,nextId;if(i>=0){queue.splice(i,1);if(queue.length>=max&&i=0){return handlerImpl.upload(id,true)}else{return this.upload(id)}},cancel:function(id){var cancelRetVal=handlerImpl.cancel(id);if(qq.isPromise(cancelRetVal)){cancelRetVal.then(function(){cancelSuccess(id)})}else if(cancelRetVal!==false){cancelSuccess(id)}},cancelAll:function(){var self=this,queueCopy=[];qq.extend(queueCopy,queue);qq.each(queueCopy,function(idx,fileId){self.cancel(fileId)});queue=[]},getName:function(id){return handlerImpl.getName(id)},getSize:function(id){if(handlerImpl.getSize){return handlerImpl.getSize(id)}},getFile:function(id){if(handlerImpl.getFile){return handlerImpl.getFile(id)}},reset:function(){log("Resetting upload handler");api.cancelAll();queue=[];handlerImpl.reset()},expunge:function(id){return handlerImpl.expunge(id)},getUuid:function(id){return handlerImpl.getUuid(id)},isValid:function(id){return handlerImpl.isValid(id)},getResumableFilesData:function(){if(handlerImpl.getResumableFilesData){return handlerImpl.getResumableFilesData()}return[]}};return api};qq.UploadHandlerForm=function(o,uploadCompleteCallback,onUuidChanged,logCallback){"use strict";var options=o,inputs=[],uuids=[],detachLoadEvents={},postMessageCallbackTimers={},uploadComplete=uploadCompleteCallback,log=logCallback,corsMessageReceiver=new qq.WindowReceiveMessage({log:log}),onloadCallbacks={},formHandlerInstanceId=qq.getUniqueId(),api;function detachLoadEvent(id){if(detachLoadEvents[id]!==undefined){detachLoadEvents[id]();delete detachLoadEvents[id]}}function registerPostMessageCallback(iframe,callback){var iframeName=iframe.id,fileId=getFileIdForIframeName(iframeName);onloadCallbacks[uuids[fileId]]=callback;detachLoadEvents[fileId]=qq(iframe).attach("load",function(){if(inputs[fileId]){log("Received iframe load event for CORS upload request (iframe name "+iframeName+")");postMessageCallbackTimers[iframeName]=setTimeout(function(){var errorMessage="No valid message received from loaded iframe for iframe name "+iframeName;log(errorMessage,"error");callback({error:errorMessage})},1e3)}});corsMessageReceiver.receiveMessage(iframeName,function(message){log("Received the following window message: '"+message+"'");var response=parseResponse(getFileIdForIframeName(iframeName),message),uuid=response.uuid,onloadCallback;if(uuid&&onloadCallbacks[uuid]){log("Handling response for iframe name "+iframeName);clearTimeout(postMessageCallbackTimers[iframeName]);delete postMessageCallbackTimers[iframeName];detachLoadEvent(iframeName);onloadCallback=onloadCallbacks[uuid];delete onloadCallbacks[uuid];corsMessageReceiver.stopReceivingMessages(iframeName);onloadCallback(response)}else if(!uuid){log("'"+message+"' does not contain a UUID - ignoring.")}})}function attachLoadEvent(iframe,callback){if(options.cors.expected){registerPostMessageCallback(iframe,callback)}else{detachLoadEvents[iframe.id]=qq(iframe).attach("load",function(){log("Received response for "+iframe.id);if(!iframe.parentNode){return}try{if(iframe.contentDocument&&iframe.contentDocument.body&&iframe.contentDocument.body.innerHTML=="false"){return}}catch(error){log("Error when attempting to access iframe during handling of upload response ("+error+")","error")}callback()})}}function getIframeContentJson(id,iframe){var response;try{var doc=iframe.contentDocument||iframe.contentWindow.document,innerHtml=doc.body.innerHTML;log("converting iframe's innerHTML to JSON");log("innerHTML = "+innerHtml);if(innerHtml&&innerHtml.match(/^ ');iframe.setAttribute("id",iframeName);iframe.style.display="none";document.body.appendChild(iframe);return iframe}function createForm(id,iframe){var params=options.paramsStore.getParams(id),protocol=options.demoMode?"GET":"POST",form=qq.toElement(''),endpoint=options.endpointStore.getEndpoint(id),url=endpoint;params[options.uuidParamName]=uuids[id];if(!options.paramsInBody){url=qq.obj2url(params,endpoint)}else{qq.obj2Inputs(params,form)}form.setAttribute("action",url);form.setAttribute("target",iframe.name);form.style.display="none";document.body.appendChild(form);return form}function expungeFile(id){delete inputs[id];delete uuids[id];delete detachLoadEvents[id];if(options.cors.expected){clearTimeout(postMessageCallbackTimers[id]);delete postMessageCallbackTimers[id];corsMessageReceiver.stopReceivingMessages(id)}var iframe=document.getElementById(getIframeName(id));if(iframe){iframe.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;");qq(iframe).remove()}}function getFileIdForIframeName(iframeName){return iframeName.split("_")[0]}function getIframeName(fileId){return fileId+"_"+formHandlerInstanceId}api={add:function(fileInput){fileInput.setAttribute("name",options.inputName);var id=inputs.push(fileInput)-1;uuids[id]=qq.getUniqueId();if(fileInput.parentNode){qq(fileInput).remove()}return id},getName:function(id){if(api.isValid(id)){return inputs[id].value.replace(/.*(\/|\\)/,"")}else{log(id+" is not a valid item ID.","error")}},isValid:function(id){return inputs[id]!==undefined},reset:function(){inputs=[];uuids=[];detachLoadEvents={};formHandlerInstanceId=qq.getUniqueId()},expunge:function(id){return expungeFile(id)},getUuid:function(id){return uuids[id]},cancel:function(id){var onCancelRetVal=options.onCancel(id,api.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeFile(id)})}else if(onCancelRetVal!==false){expungeFile(id);return true}return false},upload:function(id){var input=inputs[id],fileName=api.getName(id),iframe=createIframe(id),form;if(!input){throw new Error("file with passed id was not added, or already uploaded or cancelled")}options.onUpload(id,api.getName(id));form=createForm(id,iframe);form.appendChild(input);attachLoadEvent(iframe,function(responseFromMessage){log("iframe loaded");var response=responseFromMessage?responseFromMessage:getIframeContentJson(id,iframe);detachLoadEvent(id);if(!options.cors.expected){qq(iframe).remove()}if(!response.success){if(options.onAutoRetry(id,fileName,response)){return}}options.onComplete(id,fileName,response);uploadComplete(id)});log("Sending upload request for "+id);form.submit();qq(form).remove()}};return api};qq.UploadHandlerXhr=function(o,uploadCompleteCallback,onUuidChanged,logCallback){"use strict";var options=o,uploadComplete=uploadCompleteCallback,log=logCallback,fileState=[],cookieItemDelimiter="|",chunkFiles=options.chunking.enabled&&qq.supportedFeatures.chunking,resumeEnabled=options.resume.enabled&&chunkFiles&&qq.supportedFeatures.resume,resumeId=getResumeId(),multipart=options.forceMultipart||options.paramsInBody,api;function addChunkingSpecificParams(id,params,chunkData){var size=api.getSize(id),name=api.getName(id);params[options.chunking.paramNames.partIndex]=chunkData.part;params[options.chunking.paramNames.partByteOffset]=chunkData.start;params[options.chunking.paramNames.chunkSize]=chunkData.size;params[options.chunking.paramNames.totalParts]=chunkData.count;params[options.totalFileSizeParamName]=size;if(multipart){params[options.chunking.paramNames.filename]=name}}function addResumeSpecificParams(params){params[options.resume.paramNames.resuming]=true}function getChunk(fileOrBlob,startByte,endByte){if(fileOrBlob.slice){return fileOrBlob.slice(startByte,endByte)}else if(fileOrBlob.mozSlice){return fileOrBlob.mozSlice(startByte,endByte)}else if(fileOrBlob.webkitSlice){return fileOrBlob.webkitSlice(startByte,endByte)}}function getChunkData(id,chunkIndex){var chunkSize=options.chunking.partSize,fileSize=api.getSize(id),fileOrBlob=fileState[id].file||fileState[id].blobData.blob,startBytes=chunkSize*chunkIndex,endBytes=startBytes+chunkSize>=fileSize?fileSize:startBytes+chunkSize,totalChunks=getTotalChunks(id);return{part:chunkIndex,start:startBytes,end:endBytes,count:totalChunks,blob:getChunk(fileOrBlob,startBytes,endBytes),size:endBytes-startBytes}}function getTotalChunks(id){var fileSize=api.getSize(id),chunkSize=options.chunking.partSize;return Math.ceil(fileSize/chunkSize)}function createXhr(id){var xhr=new XMLHttpRequest;fileState[id].xhr=xhr;return xhr}function setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id){var formData=new FormData,method=options.demoMode?"GET":"POST",endpoint=options.endpointStore.getEndpoint(id),url=endpoint,name=api.getName(id),size=api.getSize(id),blobData=fileState[id].blobData;params[options.uuidParamName]=fileState[id].uuid;if(multipart){params[options.totalFileSizeParamName]=size;if(blobData){params[options.blobs.paramNames.name]=blobData.name}}if(!options.paramsInBody){if(!multipart){params[options.inputName]=name}url=qq.obj2url(params,endpoint)}xhr.open(method,url,true);if(options.cors.expected&&options.cors.sendCredentials){xhr.withCredentials=true}if(multipart){if(options.paramsInBody){qq.obj2FormData(params,formData)}formData.append(options.inputName,fileOrBlob);return formData}return fileOrBlob}function setHeaders(id,xhr){var extraHeaders=options.customHeaders,fileOrBlob=fileState[id].file||fileState[id].blobData.blob;xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Cache-Control","no-cache");if(!multipart){xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-Mime-Type",fileOrBlob.type)}qq.each(extraHeaders,function(name,val){xhr.setRequestHeader(name,val)})}function handleCompletedItem(id,response,xhr){var name=api.getName(id),size=api.getSize(id);fileState[id].attemptingResume=false;options.onProgress(id,name,size,size);options.onComplete(id,name,response,xhr);if(fileState[id]){delete fileState[id].xhr}uploadComplete(id)}function uploadNextChunk(id){var chunkIdx=fileState[id].remainingChunkIdxs[0],chunkData=getChunkData(id,chunkIdx),xhr=createXhr(id),size=api.getSize(id),name=api.getName(id),toSend,params;if(fileState[id].loaded===undefined){fileState[id].loaded=0}if(resumeEnabled&&fileState[id].file){persistChunkData(id,chunkData)}xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);xhr.upload.onprogress=function(e){if(e.lengthComputable){var totalLoaded=e.loaded+fileState[id].loaded,estTotalRequestsSize=calcAllRequestsSizeForChunkedUpload(id,chunkIdx,e.total);options.onProgress(id,name,totalLoaded,estTotalRequestsSize)}};options.onUploadChunk(id,name,getChunkDataForCallback(chunkData));params=options.paramsStore.getParams(id);addChunkingSpecificParams(id,params,chunkData);if(fileState[id].attemptingResume){addResumeSpecificParams(params)}toSend=setParamsAndGetEntityToSend(params,xhr,chunkData.blob,id);setHeaders(id,xhr);log("Sending chunked upload request for item "+id+": bytes "+(chunkData.start+1)+"-"+chunkData.end+" of "+size);xhr.send(toSend)}function calcAllRequestsSizeForChunkedUpload(id,chunkIdx,requestSize){var chunkData=getChunkData(id,chunkIdx),blobSize=chunkData.size,overhead=requestSize-blobSize,size=api.getSize(id),chunkCount=chunkData.count,initialRequestOverhead=fileState[id].initialRequestOverhead,overheadDiff=overhead-initialRequestOverhead;fileState[id].lastRequestOverhead=overhead;if(chunkIdx===0){fileState[id].lastChunkIdxProgress=0;fileState[id].initialRequestOverhead=overhead;fileState[id].estTotalRequestsSize=size+chunkCount*overhead}else if(fileState[id].lastChunkIdxProgress!==chunkIdx){fileState[id].lastChunkIdxProgress=chunkIdx;fileState[id].estTotalRequestsSize+=overheadDiff}return fileState[id].estTotalRequestsSize}function getLastRequestOverhead(id){if(multipart){return fileState[id].lastRequestOverhead}else{return 0}}function handleSuccessfullyCompletedChunk(id,response,xhr){var chunkIdx=fileState[id].remainingChunkIdxs.shift(),chunkData=getChunkData(id,chunkIdx);fileState[id].attemptingResume=false;fileState[id].loaded+=chunkData.size+getLastRequestOverhead(id);if(fileState[id].remainingChunkIdxs.length>0){uploadNextChunk(id)}else{if(resumeEnabled){deletePersistedChunkData(id)}handleCompletedItem(id,response,xhr)}}function isErrorResponse(xhr,response){return xhr.status!==200||!response.success||response.reset}function parseResponse(id,xhr){var response;try{response=qq.parseJson(xhr.responseText);if(response.newUuid!==undefined){log("Server requested UUID change from '"+fileState[id].uuid+"' to '"+response.newUuid+"'");fileState[id].uuid=response.newUuid;onUuidChanged(id,response.newUuid)}}catch(error){log("Error when attempting to parse xhr response text ("+error+")","error");response={}}return response}function handleResetResponse(id){log("Server has ordered chunking effort to be restarted on next attempt for item ID "+id,"error");if(resumeEnabled){deletePersistedChunkData(id);fileState[id].attemptingResume=false}fileState[id].remainingChunkIdxs=[];delete fileState[id].loaded;delete fileState[id].estTotalRequestsSize;delete fileState[id].initialRequestOverhead}function handleResetResponseOnResumeAttempt(id){fileState[id].attemptingResume=false;log("Server has declared that it cannot handle resume for item ID "+id+" - starting from the first chunk","error");handleResetResponse(id);api.upload(id,true)}function handleNonResetErrorResponse(id,response,xhr){var name=api.getName(id);if(options.onAutoRetry(id,name,response,xhr)){return}else{handleCompletedItem(id,response,xhr)}}function onComplete(id,xhr){var response;if(!fileState[id]){return}log("xhr - server response received for "+id);log("responseText = "+xhr.responseText);response=parseResponse(id,xhr);if(isErrorResponse(xhr,response)){if(response.reset){handleResetResponse(id)}if(fileState[id].attemptingResume&&response.reset){handleResetResponseOnResumeAttempt(id)}else{handleNonResetErrorResponse(id,response,xhr)}}else if(chunkFiles){handleSuccessfullyCompletedChunk(id,response,xhr)}else{handleCompletedItem(id,response,xhr)}}function getChunkDataForCallback(chunkData){return{partIndex:chunkData.part,startByte:chunkData.start+1,endByte:chunkData.end,totalParts:chunkData.count}}function getReadyStateChangeHandler(id,xhr){return function(){if(xhr.readyState===4){onComplete(id,xhr)}}}function persistChunkData(id,chunkData){var fileUuid=api.getUuid(id),lastByteSent=fileState[id].loaded,initialRequestOverhead=fileState[id].initialRequestOverhead,estTotalRequestsSize=fileState[id].estTotalRequestsSize,cookieName=getChunkDataCookieName(id),cookieValue=fileUuid+cookieItemDelimiter+chunkData.part+cookieItemDelimiter+lastByteSent+cookieItemDelimiter+initialRequestOverhead+cookieItemDelimiter+estTotalRequestsSize,cookieExpDays=options.resume.cookiesExpireIn;qq.setCookie(cookieName,cookieValue,cookieExpDays)}function deletePersistedChunkData(id){if(fileState[id].file){var cookieName=getChunkDataCookieName(id);qq.deleteCookie(cookieName)}}function getPersistedChunkData(id){var chunkCookieValue=qq.getCookie(getChunkDataCookieName(id)),filename=api.getName(id),sections,uuid,partIndex,lastByteSent,initialRequestOverhead,estTotalRequestsSize;if(chunkCookieValue){sections=chunkCookieValue.split(cookieItemDelimiter);if(sections.length===5){uuid=sections[0];partIndex=parseInt(sections[1],10);lastByteSent=parseInt(sections[2],10);initialRequestOverhead=parseInt(sections[3],10);estTotalRequestsSize=parseInt(sections[4],10);return{uuid:uuid,part:partIndex,lastByteSent:lastByteSent,initialRequestOverhead:initialRequestOverhead,estTotalRequestsSize:estTotalRequestsSize}}else{log("Ignoring previously stored resume/chunk cookie for "+filename+" - old cookie format","warn")}}}function getChunkDataCookieName(id){var filename=api.getName(id),fileSize=api.getSize(id),maxChunkSize=options.chunking.partSize,cookieName;cookieName="qqfilechunk"+cookieItemDelimiter+encodeURIComponent(filename)+cookieItemDelimiter+fileSize+cookieItemDelimiter+maxChunkSize;if(resumeId!==undefined){cookieName+=cookieItemDelimiter+resumeId}return cookieName}function getResumeId(){if(options.resume.id!==null&&options.resume.id!==undefined&&!qq.isFunction(options.resume.id)&&!qq.isObject(options.resume.id)){return options.resume.id}}function calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex){var currentChunkIndex;for(currentChunkIndex=getTotalChunks(id)-1;currentChunkIndex>=firstChunkIndex;currentChunkIndex-=1){fileState[id].remainingChunkIdxs.unshift(currentChunkIndex)}uploadNextChunk(id)}function onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume){firstChunkIndex=persistedChunkInfoForResume.part;fileState[id].loaded=persistedChunkInfoForResume.lastByteSent;fileState[id].estTotalRequestsSize=persistedChunkInfoForResume.estTotalRequestsSize;fileState[id].initialRequestOverhead=persistedChunkInfoForResume.initialRequestOverhead;fileState[id].attemptingResume=true;log("Resuming "+name+" at partition index "+firstChunkIndex);calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}function handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex){var name=api.getName(id),firstChunkDataForResume=getChunkData(id,persistedChunkInfoForResume.part),onResumeRetVal;onResumeRetVal=options.onResume(id,name,getChunkDataForCallback(firstChunkDataForResume));if(qq.isPromise(onResumeRetVal)){log("Waiting for onResume promise to be fulfilled for "+id);onResumeRetVal.then(function(){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)},function(){log("onResume promise fulfilled - failure indicated. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)})}else if(onResumeRetVal!==false){onResumeSuccess(id,name,firstChunkIndex,persistedChunkInfoForResume)}else{log("onResume callback returned false. Will not resume.");calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}function handleFileChunkingUpload(id,retry){var firstChunkIndex=0,persistedChunkInfoForResume;if(!fileState[id].remainingChunkIdxs||fileState[id].remainingChunkIdxs.length===0){fileState[id].remainingChunkIdxs=[];if(resumeEnabled&&!retry&&fileState[id].file){persistedChunkInfoForResume=getPersistedChunkData(id);if(persistedChunkInfoForResume){handlePossibleResumeAttempt(id,persistedChunkInfoForResume,firstChunkIndex)}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{calculateRemainingChunkIdxsAndUpload(id,firstChunkIndex)}}else{uploadNextChunk(id)}}function handleStandardFileUpload(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob,name=api.getName(id),xhr,params,toSend;fileState[id].loaded=0;xhr=createXhr(id);xhr.upload.onprogress=function(e){if(e.lengthComputable){fileState[id].loaded=e.loaded;options.onProgress(id,name,e.loaded,e.total)}};xhr.onreadystatechange=getReadyStateChangeHandler(id,xhr);params=options.paramsStore.getParams(id);toSend=setParamsAndGetEntityToSend(params,xhr,fileOrBlob,id);setHeaders(id,xhr);log("Sending upload request for "+id);xhr.send(toSend)}function expungeItem(id){var xhr=fileState[id].xhr;if(xhr){xhr.onreadystatechange=null;xhr.abort()}if(resumeEnabled){deletePersistedChunkData(id)}delete fileState[id]}api={add:function(fileOrBlobData){var id,persistedChunkData,uuid=qq.getUniqueId();if(qq.isFile(fileOrBlobData)){id=fileState.push({file:fileOrBlobData})-1}else if(qq.isBlob(fileOrBlobData.blob)){id=fileState.push({blobData:fileOrBlobData})-1}else{throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)")}if(resumeEnabled){persistedChunkData=getPersistedChunkData(id);if(persistedChunkData){uuid=persistedChunkData.uuid}}fileState[id].uuid=uuid;return id},getName:function(id){if(api.isValid(id)){var file=fileState[id].file,blobData=fileState[id].blobData;if(file){return file.fileName!==null&&file.fileName!==undefined?file.fileName:file.name}else{return blobData.name}}else{log(id+" is not a valid item ID.","error")}},getSize:function(id){var fileOrBlob=fileState[id].file||fileState[id].blobData.blob;if(qq.isFileOrInput(fileOrBlob)){return fileOrBlob.fileSize!=null?fileOrBlob.fileSize:fileOrBlob.size}else{return fileOrBlob.size}},getFile:function(id){if(fileState[id]){return fileState[id].file||fileState[id].blobData.blob}},isValid:function(id){return fileState[id]!==undefined},reset:function(){fileState=[]},expunge:function(id){return expungeItem(id)},getUuid:function(id){return fileState[id].uuid},upload:function(id,retry){var name=this.getName(id);if(this.isValid(id)){options.onUpload(id,name);if(chunkFiles){handleFileChunkingUpload(id,retry)}else{handleStandardFileUpload(id)}}},cancel:function(id){var onCancelRetVal=options.onCancel(id,this.getName(id));if(qq.isPromise(onCancelRetVal)){return onCancelRetVal.then(function(){expungeItem(id)})}else if(onCancelRetVal!==false){expungeItem(id);return true}return false},getResumableFilesData:function(){var matchingCookieNames=[],resumableFilesData=[];if(chunkFiles&&resumeEnabled){if(resumeId===undefined){matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"="))}else{matchingCookieNames=qq.getCookieNames(new RegExp("^qqfilechunk\\"+cookieItemDelimiter+".+\\"+cookieItemDelimiter+"\\d+\\"+cookieItemDelimiter+options.chunking.partSize+"\\"+cookieItemDelimiter+resumeId+"="))}qq.each(matchingCookieNames,function(idx,cookieName){var cookiesNameParts=cookieName.split(cookieItemDelimiter);var cookieValueParts=qq.getCookie(cookieName).split(cookieItemDelimiter);resumableFilesData.push({name:decodeURIComponent(cookiesNameParts[1]),size:cookiesNameParts[2],uuid:cookieValueParts[0],partIdx:cookieValueParts[1]})});return resumableFilesData}return[]}};return api};
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.4/iframe.xss.response.js b/ajax/libs/file-uploader/3.6.4/iframe.xss.response.js
new file mode 100644
index 000000000..e11fca1e5
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.4/iframe.xss.response.js
@@ -0,0 +1,6 @@
+(function() {
+ var match = /(\{.+\}).+/.exec(document.body.innerHTML);
+ if (match) {
+ parent.postMessage(match[1], '*');
+ }
+}());
diff --git a/ajax/libs/file-uploader/3.6.4/iframe.xss.response.min.js b/ajax/libs/file-uploader/3.6.4/iframe.xss.response.min.js
new file mode 100644
index 000000000..345cf3603
--- /dev/null
+++ b/ajax/libs/file-uploader/3.6.4/iframe.xss.response.min.js
@@ -0,0 +1 @@
+!function(){var match=/(\{.+\}).+/.exec(document.body.innerHTML);if(match){parent.postMessage(match[1],"*")}}();
\ No newline at end of file
diff --git a/ajax/libs/file-uploader/3.6.4/loading.gif b/ajax/libs/file-uploader/3.6.4/loading.gif
new file mode 100644
index 000000000..6fba77609
Binary files /dev/null and b/ajax/libs/file-uploader/3.6.4/loading.gif differ
diff --git a/ajax/libs/file-uploader/3.6.4/processing.gif b/ajax/libs/file-uploader/3.6.4/processing.gif
new file mode 100644
index 000000000..7c99504e1
Binary files /dev/null and b/ajax/libs/file-uploader/3.6.4/processing.gif differ
diff --git a/ajax/libs/file-uploader/3.7.0/edit.gif b/ajax/libs/file-uploader/3.7.0/edit.gif
new file mode 100644
index 000000000..13aa89a34
Binary files /dev/null and b/ajax/libs/file-uploader/3.7.0/edit.gif differ
diff --git a/ajax/libs/file-uploader/3.7.0/fineuploader-jquery.js b/ajax/libs/file-uploader/3.7.0/fineuploader-jquery.js
new file mode 100644
index 000000000..2397b3a60
--- /dev/null
+++ b/ajax/libs/file-uploader/3.7.0/fineuploader-jquery.js
@@ -0,0 +1,5807 @@
+/*!
+ * Fine Uploader
+ *
+ * Copyright 2013, Widen Enterprises, Inc. info@fineuploader.com
+ *
+ * Version: 3.7.0
+ *
+ * Homepage: http://fineuploader.com
+ *
+ * Repository: git://github.com/Widen/fine-uploader.git
+ *
+ * Licensed under GNU GPL v3, see LICENSE
+ */
+
+
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // The [W3C spec](http://www.w3.org/TR/domcore/#dom-node-contains)
+ // says a `null` (or ostensibly `undefined`) parameter
+ // passed into `Node.contains` should result in a false return value.
+ // IE7 throws an exception if the parameter is `undefined` though.
+ if (!descendant) {
+ return false;
+ }
+
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity != null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable && !variable.nodeType && Object.prototype.toString.call(variable) === '[object Object]';
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isArray = function(variable) {
+ "use strict";
+ return Object.prototype.toString.call(variable) === "[object Array]";
+}
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+
+// Returns a string, swapping argument values with the associated occurrence of {} in the passed string.
+qq.format = function(str) {
+ "use strict";
+
+ var args = Array.prototype.slice.call(arguments, 1),
+ newStr = str;
+
+ qq.each(args, function(idx, val) {
+ newStr = newStr.replace(/{}/, val);
+ });
+
+ return newStr;
+};
+
+qq.isFile = function(maybeFile) {
+ "use strict";
+
+ return window.File && Object.prototype.toString.call(maybeFile) === '[object File]'
+};
+
+qq.isFileList = function(maybeFileList) {
+ return window.FileList && Object.prototype.toString.call(maybeFileList) === '[object FileList]'
+}
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+
+ return qq.isFile(maybeFileOrInput) || qq.isInput(maybeFileOrInput);
+};
+
+qq.isInput = function(maybeInput) {
+ if (window.HTMLInputElement) {
+ if (Object.prototype.toString.call(maybeInput) === '[object HTMLInputElement]') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ if (maybeInput.tagName) {
+ if (maybeInput.tagName.toLowerCase() === 'input') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && Object.prototype.toString.call(maybeBlob) === '[object Blob]';
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice !== undefined || File.prototype.webkitSlice !== undefined || File.prototype.mozSlice !== undefined);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+
+ return first;
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+qq.ios = function() {
+ "use strict";
+ return navigator.userAgent.indexOf("iPad") !== -1
+ || navigator.userAgent.indexOf("iPod") !== -1
+ || navigator.userAgent.indexOf("iPhone") !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(objOrArray, callback) {
+ "use strict";
+ var keyOrIndex, retVal;
+ if (objOrArray) {
+ if (qq.isArray(objOrArray)) {
+ for (keyOrIndex = 0; keyOrIndex < objOrArray.length; keyOrIndex++) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ else {
+ for (keyOrIndex in objOrArray) {
+ if (Object.prototype.hasOwnProperty.call(objOrArray, keyOrIndex)) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+ }
+};
+
+//include any args that should be passed to the new function after the context arg
+qq.bind = function(oldFunc, context) {
+ if (qq.isFunction(oldFunc)) {
+ var args = Array.prototype.slice.call(arguments, 2);
+
+ return function() {
+ if (arguments.length) {
+ args = args.concat(Array.prototype.slice.call(arguments))
+ }
+ return oldFunc.apply(context, args);
+ };
+ }
+
+ throw new Error("first parameter must be a function!");
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ cookie;
+
+ qq.each(ca, function(idx, part) {
+ var cookiePart = part;
+ while (cookiePart.charAt(0)==' ') {
+ cookiePart = cookiePart.substring(1, cookiePart.length);
+ }
+
+ if (cookiePart.indexOf(nameEQ) === 0) {
+ cookie = cookiePart.substring(nameEQ.length, cookiePart.length);
+ return false;
+ }
+ });
+
+ return cookie;
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+;qq.version="3.7.0";;qq.supportedFeatures = (function () {
+ var supportsUploading,
+ supportsAjaxFileUploading,
+ supportsFolderDrop,
+ supportsChunking,
+ supportsResume,
+ supportsUploadViaPaste,
+ supportsUploadCors,
+ supportsDeleteFileXdr,
+ supportsDeleteFileCorsXhr,
+ supportsDeleteFileCors;
+
+
+ function testSupportsFileInputElement() {
+ var supported = true,
+ tempInput;
+
+ try {
+ tempInput = document.createElement('input');
+ tempInput.type = 'file';
+ qq(tempInput).hide();
+
+ if (tempInput.disabled) {
+ supported = false;
+ }
+ }
+ catch (ex) {
+ supported = false;
+ }
+
+ return supported;
+ }
+
+ //only way to test for Filesystem API support since webkit does not expose the DataTransfer interface
+ function isChrome21OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[2][1-9]|Chrome\/[3-9][0-9]/) !== undefined;
+ }
+
+ //only way to test for complete Clipboard API support at this time
+ function isChrome14OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/) !== undefined;
+ }
+
+ //Ensure we can send cross-origin `XMLHttpRequest`s
+ function isCrossOriginXhrSupported() {
+ if (window.XMLHttpRequest) {
+ var xhr = new XMLHttpRequest();
+
+ //Commonly accepted test for XHR CORS support.
+ return xhr.withCredentials !== undefined;
+ }
+
+ return false;
+ }
+
+ //Test for (terrible) cross-origin ajax transport fallback for IE9 and IE8
+ function isXdrSupported() {
+ return window.XDomainRequest !== undefined;
+ }
+
+ // CORS Ajax requests are supported if it is either possible to send credentialed `XMLHttpRequest`s,
+ // or if `XDomainRequest` is an available alternative.
+ function isCrossOriginAjaxSupported() {
+ if (isCrossOriginXhrSupported()) {
+ return true;
+ }
+
+ return isXdrSupported();
+ }
+
+
+ supportsUploading = testSupportsFileInputElement();
+
+ supportsAjaxFileUploading = supportsUploading && qq.isXhrUploadSupported();
+
+ supportsFolderDrop = supportsAjaxFileUploading && isChrome21OrHigher();
+
+ supportsChunking = supportsAjaxFileUploading && qq.isFileChunkingSupported();
+
+ supportsResume = supportsAjaxFileUploading && supportsChunking && qq.areCookiesEnabled();
+
+ supportsUploadViaPaste = supportsAjaxFileUploading && isChrome14OrHigher();
+
+ supportsUploadCors = supportsUploading && (window.postMessage !== undefined || supportsAjaxFileUploading);
+
+ supportsDeleteFileCorsXhr = isCrossOriginXhrSupported();
+
+ supportsDeleteFileXdr = isXdrSupported();
+
+ supportsDeleteFileCors = isCrossOriginAjaxSupported();
+
+
+ return {
+ uploading: supportsUploading,
+ ajaxUploading: supportsAjaxFileUploading,
+ fileDrop: supportsAjaxFileUploading, //NOTE: will also return true for touch-only devices. It's not currently possible to accurately test for touch-only devices
+ folderDrop: supportsFolderDrop,
+ chunking: supportsChunking,
+ resume: supportsResume,
+ uploadCustomHeaders: supportsAjaxFileUploading,
+ uploadNonMultipart: supportsAjaxFileUploading,
+ itemSizeValidation: supportsAjaxFileUploading,
+ uploadViaPaste: supportsUploadViaPaste,
+ progressBar: supportsAjaxFileUploading,
+ uploadCors: supportsUploadCors,
+ deleteFileCorsXhr: supportsDeleteFileCorsXhr,
+ deleteFileCorsXdr: supportsDeleteFileXdr, //NOTE: will also return true in IE10, where XDR is also supported
+ deleteFileCors: supportsDeleteFileCors,
+ canDetermineSize: supportsAjaxFileUploading
+ }
+
+}());
+;/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallbacks = [],
+ failureCallbacks = [],
+ doneCallbacks = [],
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ if (onSuccess) {
+ successCallbacks.push(onSuccess);
+ }
+ if (onFailure) {
+ failureCallbacks.push(onFailure);
+ }
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+
+ return this;
+ },
+
+ done: function(callback) {
+ if (state === 0) {
+ doneCallbacks.push(callback);
+ }
+ else {
+ callback();
+ }
+
+ return this;
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallbacks.length) {
+ qq.each(successCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallbacks.length) {
+ qq.each(failureCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ }
+ };
+};
+
+qq.isPromise = function(maybePromise) {
+ return maybePromise && maybePromise.then && maybePromise.done;
+};;/*globals qq*/
+
+/**
+ * This module represents an upload or "Select File(s)" button. It's job is to embed an opaque ` `
+ * element as a child of a provided "container" element. This "container" element (`options.element`) is used to provide
+ * a custom style for the ` ` element. The ability to change the style of the container element is also
+ * provided here by adding CSS classes to the container on hover/focus.
+ *
+ * TODO Eliminate the mouseover and mouseout event handlers since the :hover CSS pseudo-class should now be
+ * available on all supported browsers.
+ *
+ * @param o Options to override the default values
+ */
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ // Used to detach all event handlers created at once for this instance
+ disposeSupport = new qq.DisposeSupport(),
+
+ options = {
+ // "Container" element
+ element: null,
+
+ // If true adds `multiple` attribute to ` `
+ multiple: false,
+
+ // Corresponds to the `accept` attribute on the associated ` `
+
+ acceptFiles: null,
+
+ // `name` attribute of ` `
+ name: 'qqfile',
+
+ // Called when the browser invokes the onchange handler on the ` `
+ onChange: function(input) {},
+
+ // **This option will be removed** in the future as the :hover CSS pseudo-class is available on all supported browsers
+ hoverClass: 'qq-upload-button-hover',
+
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ // Overrides any of the default option values with any option values passed in during construction.
+ qq.extend(options, o);
+
+
+ // Embed an opaque ` ` element as a child of `options.element`.
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ // **These event handlers will be removed** in the future as the :hover CSS pseudo-class is available on all supported browsers
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent) {
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+ // Make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+
+ // Exposed API
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+;/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};;qq.UploadData = function(uploaderProxy) {
+ var data = [],
+ byId = {},
+ byUuid = {},
+ byStatus = {},
+ api;
+
+ function getDataByIds(ids) {
+ if (qq.isArray(ids)) {
+ var entries = [];
+
+ qq.each(ids, function(idx, id) {
+ entries.push(data[byId[id]]);
+ });
+
+ return entries;
+ }
+
+ return data[byId[ids]];
+ }
+
+ function getDataByUuids(uuids) {
+ if (qq.isArray(uuids)) {
+ var entries = [];
+
+ qq.each(uuids, function(idx, uuid) {
+ entries.push(data[byUuid[uuid]]);
+ });
+
+ return entries;
+ }
+
+ return data[byUuid[uuids]];
+ }
+
+ function getDataByStatus(status) {
+ var statusResults = [],
+ statuses = [].concat(status);
+
+ qq.each(statuses, function(index, statusEnum) {
+ var statusResultIndexes = byStatus[statusEnum];
+
+ if (statusResultIndexes !== undefined) {
+ qq.each(statusResultIndexes, function(i, dataIndex) {
+ statusResults.push(data[dataIndex]);
+ });
+ }
+ });
+
+ return statusResults;
+ }
+
+ api = {
+ added: function(id) {
+ var uuid = uploaderProxy.getUuid(id),
+ name = uploaderProxy.getName(id),
+ size = uploaderProxy.getSize(id),
+ status = qq.status.SUBMITTING;
+
+ var index = data.push({
+ id: id,
+ name: name,
+ originalName: name,
+ uuid: uuid,
+ size: size,
+ status: status
+ }) - 1;
+
+ byId[id] = index;
+
+ byUuid[uuid] = index;
+
+ if (byStatus[status] === undefined) {
+ byStatus[status] = [];
+ }
+ byStatus[status].push(index);
+
+ uploaderProxy.onStatusChange(id, undefined, status);
+ },
+
+ retrieve: function(optionalFilter) {
+ if (qq.isObject(optionalFilter) && data.length) {
+ if (optionalFilter.id !== undefined) {
+ return getDataByIds(optionalFilter.id);
+ }
+
+ else if (optionalFilter.uuid !== undefined) {
+ return getDataByUuids(optionalFilter.uuid);
+ }
+
+ else if (optionalFilter.status) {
+ return getDataByStatus(optionalFilter.status);
+ }
+ }
+ else {
+ return qq.extend([], data, true);
+ }
+ },
+
+ reset: function() {
+ data = [];
+ byId = {};
+ byUuid = {};
+ byStatus = {};
+ },
+
+ setStatus: function(id, newStatus) {
+ var dataIndex = byId[id],
+ oldStatus = data[dataIndex].status,
+ byStatusOldStatusIndex = qq.indexOf(byStatus[oldStatus], dataIndex);
+
+ byStatus[oldStatus].splice(byStatusOldStatusIndex, 1);
+
+ data[dataIndex].status = newStatus;
+
+ if (byStatus[newStatus] === undefined) {
+ byStatus[newStatus] = [];
+ }
+ byStatus[newStatus].push(dataIndex);
+
+ uploaderProxy.onStatusChange(id, oldStatus, newStatus);
+ },
+
+ uuidChanged: function(id, newUuid) {
+ var dataIndex = byId[id],
+ oldUuid = data[dataIndex].uuid;
+
+ data[dataIndex].uuid = newUuid;
+ byUuid[newUuid] = dataIndex;
+ delete byUuid[oldUuid];
+ },
+
+ nameChanged: function(id, newName) {
+ var dataIndex = byId[id];
+
+ data[dataIndex].name = newName;
+ }
+ };
+
+ return api;
+};
+
+qq.status = {
+ SUBMITTING: "submitting",
+ SUBMITTED: "submitted",
+ REJECTED: "rejected",
+ QUEUED: "queued",
+ CANCELED: "canceled",
+ UPLOADING: "uploading",
+ UPLOAD_RETRYING: "retrying upload",
+ UPLOAD_SUCCESSFUL: "upload successful",
+ UPLOAD_FAILED: "upload failed",
+ DELETE_FAILED: "delete failed",
+ DELETING: "deleting",
+ DELETED: "deleted"
+};
+;qq.FineUploaderBasic = function(o) {
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize',
+ filenameParam: 'qqfilename'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true,
+ acceptFiles: null
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON, maybeXhr){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhrOrXdr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhrOrXdr, isError){},
+ onPasteReceived: function(blob) {},
+ onStatusChange: function(id, oldStatus, newStatus) {}
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName !== undefined && fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ defaultResponseError: "Upload failure reason unknown",
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ method: "DELETE",
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false,
+ allowXdr: false
+ },
+ blobs: {
+ defaultName: 'misc_data'
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ },
+ camera: {
+ ios: false
+ }
+ };
+
+ qq.extend(this._options, o, true);
+
+ this._handleCameraAccess();
+
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData = this._createUploadDataTracker();
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader ' + qq.version + '] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader ' + qq.version + '] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function() {
+ return this._filesInProgress.length;
+ },
+ getNetUploads: function() {
+ return this._netUploaded;
+ },
+ uploadStoredFiles: function() {
+ var idToUpload;
+
+ if (this._storedIds.length === 0) {
+ this._itemError('noFilesError');
+ }
+ else {
+ while (this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netUploadedOrQueued++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData.reset();
+
+ if (this._pasteHandler) {
+ this._pasteHandler.reset();
+ }
+ },
+ addFiles: function(filesOrInputs, params, endpoint) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ fileOrInputIndex, fileOrInput, fileIndex;
+
+ if (filesOrInputs) {
+ if (!qq.isFileList(filesOrInputs)) {
+ filesOrInputs = [].concat(filesOrInputs);
+ }
+
+ for (fileOrInputIndex = 0; fileOrInputIndex < filesOrInputs.length; fileOrInputIndex+=1) {
+ fileOrInput = filesOrInputs[fileOrInputIndex];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ if (qq.isInput(fileOrInput) && qq.supportedFeatures.ajaxUploading) {
+ for (fileIndex = 0; fileIndex < fileOrInput.files.length; fileIndex++) {
+ verifiedFilesOrInputs.push(fileOrInput.files[fileIndex]);
+ }
+ }
+ else {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Received ' + verifiedFilesOrInputs.length + ' files or inputs.');
+ this._prepareItemsForUpload(verifiedFilesOrInputs, params, endpoint);
+ }
+ },
+ addBlobs: function(blobDataOrArray, params, endpoint) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._prepareItemsForUpload(verifiedBlobDataList, params, endpoint);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ setName: function(id, newName) {
+ this._handler.setName(id, newName);
+ this._uploadData.nameChanged(id, newName);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ doesExist: function(fileOrBlobId) {
+ return this._handler.isValid(fileOrBlobId);
+ },
+ getUploads: function(optionalFilter) {
+ return this._uploadData.retrieve(optionalFilter);
+ },
+ _handleCheckedCallback: function(details) {
+ var self = this,
+ callbackRetVal = details.callback();
+
+ if (qq.isPromise(callbackRetVal)) {
+ this.log(details.name + " - waiting for " + details.name + " promise to be fulfilled for " + details.identifier);
+ return callbackRetVal.then(
+ function(successParam) {
+ self.log(details.name + " promise success for " + details.identifier);
+ details.onSuccess(successParam);
+ },
+ function() {
+ if (details.onFailure) {
+ self.log(details.name + " promise failure for " + details.identifier);
+ details.onFailure();
+ }
+ else {
+ self.log(details.name + " promise failure for " + details.identifier);
+ }
+ });
+ }
+
+ if (callbackRetVal !== false) {
+ details.onSuccess(callbackRetVal);
+ }
+ else {
+ if (details.onFailure) {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Invoking failure callback.")
+ details.onFailure();
+ }
+ else {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Will not proceed.")
+ }
+ }
+
+ return callbackRetVal;
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.supportedFeatures.ajaxUploading,
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ filenameParam: this._options.request.filenameParam,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result, xhr);
+ },
+ onCancel: function(id, name) {
+ return self._handleCheckedCallback({
+ name: "onCancel",
+ callback: qq.bind(self._options.callbacks.onCancel, self, id, name),
+ onSuccess: qq.bind(self._onCancel, self, id, name),
+ identifier: id
+ });
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ onUuidChanged: function(id, newUuid) {
+ self._uploadData.uuidChanged(id, newUuid);
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ method: this._options.deleteFile.method,
+ maxConnections: this._options.maxConnections,
+ uuidParamName: this._options.request.uuidName,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhrOrXdr, isError) {
+ self._onDeleteComplete(id, xhrOrXdr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhrOrXdr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ self._handleCheckedCallback({
+ name: "onPasteReceived",
+ callback: qq.bind(self._options.callbacks.onPasteReceived, self, blob),
+ onSuccess: qq.bind(self._handlePasteSuccess, self, blob),
+ identifier: "pasted image"
+ });
+ }
+ }
+ });
+ },
+ _createUploadDataTracker: function() {
+ var self = this;
+
+ return new qq.UploadData({
+ getName: function(id) {
+ return self.getName(id);
+ },
+ getUuid: function(id) {
+ return self.getUuid(id);
+ },
+ getSize: function(id) {
+ return self.getSize(id);
+ },
+ onStatusChange: function(id, oldStatus, newStatus) {
+ self._onUploadStatusChange(id, oldStatus, newStatus);
+ self._options.callbacks.onStatusChange(id, oldStatus, newStatus);
+ }
+ });
+ },
+ _onUploadStatusChange: function(id, oldStatus, newStatus) {
+ //nothing to do in the basic uploader
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total) {
+ //nothing to do yet in core uploader
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._netUploadedOrQueued--;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_FAILED);
+ }
+ else {
+ this._netUploaded++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_SUCCESSFUL);
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name) {
+ this._netUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+
+ this._uploadData.setStatus(id, qq.status.CANCELED);
+ },
+ _isDeletePossible: function() {
+ if (!this._options.deleteFile.enabled) {
+ return false;
+ }
+
+ if (this._options.cors.expected) {
+ if (qq.supportedFeatures.deleteFileCorsXhr) {
+ return true;
+ }
+
+ if (qq.supportedFeatures.deleteFileCorsXdr && this._options.cors.allowXdr) {
+ return true;
+ }
+
+ return false;
+ }
+
+ return true;
+ },
+ _onSubmitDelete: function(id, onSuccessCallback) {
+ if (this._isDeletePossible()) {
+ return this._handleCheckedCallback({
+ name: "onSubmitDelete",
+ callback: qq.bind(this._options.callbacks.onSubmitDelete, this, id),
+ onSuccess: onSuccessCallback || qq.bind(this._deleteHandler.sendDelete, this, id, this.getUuid(id)),
+ identifier: id
+ });
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(id) {
+ this._uploadData.setStatus(id, qq.status.DELETING);
+ },
+ _onDeleteComplete: function(id, xhrOrXdr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this._uploadData.setStatus(id, qq.status.DELETE_FAILED);
+ this.log("Delete request for '" + name + "' has failed.", "error");
+
+ // For error reporing, we only have accesss to the response status if this is not
+ // an `XDomainRequest`.
+ if (xhrOrXdr.withCredentials === undefined) {
+ this._options.callbacks.onError(id, name, "Delete request failed", xhrOrXdr);
+ }
+ else {
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhrOrXdr.status, xhrOrXdr);
+ }
+ }
+ else {
+ this._netUploadedOrQueued--;
+ this._netUploaded--;
+ this._handler.expunge(id);
+ this._uploadData.setStatus(id, qq.status.DELETED);
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.UPLOADING);
+ },
+ _onInputChange: function(input){
+ if (qq.supportedFeatures.ajaxUploading) {
+ this.addFiles(input.files);
+ }
+ else {
+ this.addFiles(input);
+ }
+
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : this._options.text.defaultResponseError;
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ var validationDescriptors = this._getValidationDescriptors(items);
+
+ this._handleCheckedCallback({
+ name: "onValidateBatch",
+ callback: qq.bind(this._options.callbacks.onValidateBatch, this, validationDescriptors),
+ onSuccess: qq.bind(this._onValidateBatchCallbackSuccess, this, validationDescriptors, items, params, endpoint),
+ identifier: "batch validation"
+ });
+ },
+ _upload: function(blobOrFileContainer, params, endpoint) {
+ var id = this._handler.add(blobOrFileContainer),
+ name = this._handler.getName(id);
+
+ this._uploadData.added(id);
+
+ if (params) {
+ this.setParams(params, id);
+ }
+
+ if (endpoint) {
+ this.setEndpoint(endpoint, id);
+ }
+
+ this._handleCheckedCallback({
+ name: "onSubmit",
+ callback: qq.bind(this._options.callbacks.onSubmit, this, id, name),
+ onSuccess: qq.bind(this._onSubmitCallbackSuccess, this, id, name),
+ onFailure: qq.bind(this._fileOrBlobRejected, this, id, name),
+ identifier: id
+ });
+ },
+ _onSubmitCallbackSuccess: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.SUBMITTED);
+
+ this._onSubmit.apply(this, arguments);
+ this._onSubmitted.apply(this, arguments);
+ this._options.callbacks.onSubmitted.apply(this, arguments);
+
+ if (this._options.autoUpload) {
+ if (!this._handler.upload(id)) {
+ this._uploadData.setStatus(id, qq.status.QUEUED);
+ }
+ }
+ else {
+ this._storeForLater(id);
+ }
+ },
+ _onSubmitted: function(id) {
+ //nothing to do in the base uploader
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _onValidateBatchCallbackSuccess: function(validationDescriptors, items, params, endpoint) {
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netUploadedOrQueued + validationDescriptors.length;
+
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ if (items.length > 0) {
+ this._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(this._options.callbacks.onValidate, this, items[0]),
+ onSuccess: qq.bind(this._onValidateCallbackSuccess, this, items, 0, params, endpoint),
+ onFailure: qq.bind(this._onValidateCallbackFailure, this, items, 0, params, endpoint),
+ identifier: "Item '" + items[0].name + "', size: " + items[0].size
+ });
+ }
+ else {
+ this._itemError("noFilesError");
+ }
+ }
+ else {
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ },
+ _onValidateCallbackSuccess: function(items, index, params, endpoint) {
+ var nextIndex = index+1,
+ validationDescriptor = this._getValidationDescriptor(items[index]),
+ validItem = false;
+
+ if (this._validateFileOrBlobData(items[index], validationDescriptor)) {
+ validItem = true;
+ this._upload(items[index], params, endpoint);
+ }
+
+ this._maybeProcessNextItemAfterOnValidateCallback(validItem, items, nextIndex, params, endpoint);
+ },
+ _onValidateCallbackFailure: function(items, index, params, endpoint) {
+ var nextIndex = index+ 1;
+
+ this._fileOrBlobRejected(undefined, items[0].name);
+
+ this._maybeProcessNextItemAfterOnValidateCallback(false, items, nextIndex, params, endpoint);
+ },
+ _maybeProcessNextItemAfterOnValidateCallback: function(validItem, items, index, params, endpoint) {
+ var self = this;
+
+ if (items.length > index) {
+ if (validItem || !this._options.validation.stopOnFirstInvalidFile) {
+ //use setTimeout to prevent a stack overflow with a large number of files in the batch & non-promissory callbacks
+ setTimeout(function() {
+ var validationDescriptor = self._getValidationDescriptor(items[index]);
+
+ self._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(self._options.callbacks.onValidate, self, items[index]),
+ onSuccess: qq.bind(self._onValidateCallbackSuccess, self, items, index, params, endpoint),
+ onFailure: qq.bind(self._onValidateCallbackFailure, self, items, index, params, endpoint),
+ identifier: "Item '" + validationDescriptor.name + "', size: " + validationDescriptor.size
+ });
+ }, 0);
+ }
+ }
+ },
+ _validateFileOrBlobData: function(item, validationDescriptor) {
+ var name = validationDescriptor.name,
+ size = validationDescriptor.size,
+ valid = true;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ valid = false;
+ }
+
+ if (qq.isFileOrInput(item) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ valid = false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ valid = false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ valid = false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ valid = false;
+ }
+
+ if (!valid) {
+ this._fileOrBlobRejected(undefined, name);
+ }
+
+ return valid;
+ },
+ _fileOrBlobRejected: function(id, name) {
+ if (id !== undefined) {
+ this._uploadData.setStatus(id, qq.status.REJECTED);
+ }
+ },
+ _itemError: function(code, maybeNameOrNames) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ names = [].concat(maybeNameOrNames),
+ name = names[0],
+ extensionsForMessage, placeholderMatch;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ placeholderMatch = message.match(/(\{\w+\})/g);
+ if (placeholderMatch !== null) {
+ qq.each(placeholderMatch, function(idx, placeholder) {
+ r(placeholder, names[idx]);
+ });
+ }
+
+ this._options.callbacks.onError(null, name, message, undefined);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message, undefined);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1000;
+ i++;
+ } while (bytes > 999);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size !== undefined) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ },
+ _handleCameraAccess: function() {
+ if (this._options.camera.ios && qq.ios()) {
+ this._options.multiple = false;
+
+ if (this._options.validation.acceptFiles === null) {
+ this._options.validation.acceptFiles = "image/*;capture=camera";
+ }
+ else {
+ this._options.validation.acceptFiles += ",image/*;capture=camera";
+ }
+ }
+ }
+};
+;/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz,
+ droppedFiles = [],
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropZoneElements: [],
+ hideDropZonesBeforeEnter: false,
+ allowMultipleItems: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: new qq.DragAndDrop.callbacks()
+ };
+
+ qq.extend(options, o, true);
+
+ setupDragDrop();
+
+ function uploadDroppedFiles(files) {
+ options.callbacks.dropLog('Grabbed ' + files.length + " dropped files.");
+ dz.dropDisabled(false);
+ options.callbacks.processingDroppedFilesComplete(files);
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i,
+ parseEntryPromise = new qq.Promise();
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ droppedFiles.push(file);
+ parseEntryPromise.success();
+ },
+ function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+ else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ var entriesLeft = entries.length;
+
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]).done(function() {
+ entriesLeft-=1;
+
+ if (entriesLeft === 0) {
+ parseEntryPromise.success();
+ }
+ });
+ }
+
+ if (!entries.length) {
+ parseEntryPromise.success();
+ }
+ }, function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+
+ return parseEntryPromise;
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry,
+ pendingFolderPromises = [],
+ handleDataTransferPromise = new qq.Promise();
+
+ options.callbacks.processingDroppedFiles();
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.allowMultipleItems) {
+ options.callbacks.processingDroppedFilesComplete([]);
+ options.callbacks.dropError('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ handleDataTransferPromise.failure();
+ }
+ else {
+ droppedFiles = [];
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ }
+
+ else {
+ pendingFolderPromises.push(traverseFileTree(entry).done(function() {
+ pendingFolderPromises.pop();
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }));
+ }
+ }
+ }
+ }
+ else {
+ droppedFiles = dataTransfer.files;
+ }
+
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }
+
+ return handleDataTransferPromise;
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer).done(function() {
+ uploadDroppedFiles(droppedFiles);
+ });
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ var dropZones = options.dropZoneElements;
+
+ qq.each(dropZones, function(idx, dropZone) {
+ setupDropzone(dropZone);
+ })
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (dropZones.length && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).css({display: 'block'});
+ });
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropZonesBeforeEnter && qq.FineUploader.prototype._leaving_document_out(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setupExtraDropzone: function(element) {
+ options.dropZoneElements.push(element);
+ setupDropzone(element);
+ },
+
+ removeDropzone: function(element) {
+ var i,
+ dzs = options.dropZoneElements;
+
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+qq.DragAndDrop.callbacks = function() {
+ return {
+ processingDroppedFiles: function() {},
+ processingDroppedFilesComplete: function(files) {},
+ dropError: function(code, errorSpecifics) {
+ qq.log("Drag & drop error code '" + code + " with these specifics: '" + errorSpecifics + "'", "error");
+ },
+ dropLog: function(message, level) {
+ qq.log(message, level);
+ }
+ }
+}
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+;/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ (this._options.editFilename && this._options.editFilename.enabled ? ' ' : '') +
+ ' ' +
+ (this._options.editFilename && this._options.editFilename.enabled ? ' ' : '') +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+ editFilenameInput: 'qq-edit-filename',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+ editNameIcon: 'qq-edit-filename-icon',
+ editable: 'qq-editable',
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file",
+ unsupportedBrowser: "Unrecoverable error - this browser does not permit file uploading of any kind."
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false,
+ prependFiles: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ editFilename: {
+ enabled: false
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+
+ if (!qq.supportedFeatures.uploading || (this._options.cors.expected && !qq.supportedFeatures.uploadCors)) {
+ this._options.element.innerHTML = "" + this._options.messages.unsupportedBrowser + "
"
+ }
+ else {
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._deleteRetryOrCancelClickHandler = this._bindDeleteRetryOrCancelClickEvent();
+
+ // A better approach would be to check specifically for focusin event support by querying the DOM API,
+ // but the DOMFocusIn event is not exposed as a property, so we have to resort to UA string sniffing.
+ this._focusinEventSupported = !qq.firefox();
+
+ if (this._isEditFilenameEnabled()) {
+ this._filenameClickHandler = this._bindFilenameClickEvent();
+ this._filenameInputFocusInHandler = this._bindFilenameInputFocusInEvent();
+ this._filenameInputFocusHandler = this._bindFilenameInputFocusEvent();
+ }
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dropZoneElements = this._options.dragAndDrop.extraDropzones,
+ preventSelectFiles;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ dropZoneElements.push(this._find(this._options.element, 'drop'));
+ }
+
+ return new qq.DragAndDrop({
+ dropZoneElements: dropZoneElements,
+ hideDropZonesBeforeEnter: this._options.dragAndDrop.hideDropzones,
+ allowMultipleItems: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ processingDroppedFiles: function() {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ },
+ processingDroppedFilesComplete: function(files) {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ dropError: function(code, errorData) {
+ self._itemError(code, errorData);
+ },
+ dropLog: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+ },
+ _bindDeleteRetryOrCancelClickEvent: function() {
+ var self = this;
+
+ return new qq.DeleteRetryOrCancelClickHandler({
+ listElement: this._listElement,
+ classes: this._classes,
+ log: function(message, lvl) {
+ self.log(message, lvl);
+ },
+ onDeleteFile: function(fileId) {
+ self.deleteFile(fileId);
+ },
+ onCancel: function(fileId) {
+ self.cancel(fileId);
+ },
+ onRetry: function(fileId) {
+ var item = self.getItemByFileId(fileId);
+
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(fileId);
+ },
+ onGetName: function(fileId) {
+ return self.getName(fileId);
+ }
+ });
+ },
+ _isEditFilenameEnabled: function() {
+ return this._options.editFilename.enabled && !this._options.autoUpload;
+ },
+ _filenameEditHandler: function() {
+ var self = this;
+
+ return {
+ listElement: this._listElement,
+ classes: this._classes,
+ log: function(message, lvl) {
+ self.log(message, lvl);
+ },
+ onGetUploadStatus: function(fileId) {
+ return self.getUploads({id: fileId}).status;
+ },
+ onGetName: function(fileId) {
+ return self.getName(fileId);
+ },
+ onSetName: function(fileId, newName) {
+ var item = self.getItemByFileId(fileId),
+ qqFilenameDisplay = qq(self._find(item, 'file')),
+ formattedFilename = self._options.formatFileName(newName);
+
+ qqFilenameDisplay.setText(formattedFilename);
+ self.setName(fileId, newName);
+ },
+ onGetInput: function(item) {
+ return self._find(item, 'editFilenameInput');
+ },
+ onEditingStatusChange: function(fileId, isEditing) {
+ var item = self.getItemByFileId(fileId),
+ qqInput = qq(self._find(item, 'editFilenameInput')),
+ qqFilenameDisplay = qq(self._find(item, 'file')),
+ qqEditFilenameIcon = qq(self._find(item, 'editNameIcon')),
+ editableClass = self._classes.editable;
+
+ if (isEditing) {
+ qqInput.addClass('qq-editing');
+
+ qqFilenameDisplay.hide();
+ qqEditFilenameIcon.removeClass(editableClass);
+ }
+ else {
+ qqInput.removeClass('qq-editing');
+ qqFilenameDisplay.css({display: ''});
+ qqEditFilenameIcon.addClass(editableClass);
+ }
+
+ // Force IE8 and older to repaint
+ qq(item).addClass('qq-temp').removeClass('qq-temp');
+ }
+ };
+ },
+ _onUploadStatusChange: function(id, oldStatus, newStatus) {
+ if (this._isEditFilenameEnabled()) {
+ var item = this.getItemByFileId(id),
+ editableClass = this._classes.editable,
+ qqFilenameDisplay, qqEditFilenameIcon;
+
+ // Status for a file exists before it has been added to the DOM, so we must be careful here.
+ if (item && newStatus !== qq.status.SUBMITTED) {
+ qqFilenameDisplay = qq(this._find(item, 'file'));
+ qqEditFilenameIcon = qq(this._find(item, 'editNameIcon'));
+
+ qqFilenameDisplay.removeClass(editableClass);
+ qqEditFilenameIcon.removeClass(editableClass);
+ }
+ }
+ },
+ _bindFilenameInputFocusInEvent: function() {
+ var spec = qq.extend({}, this._filenameEditHandler());
+
+ return new qq.FilenameInputFocusInHandler(spec);
+ },
+ _bindFilenameInputFocusEvent: function() {
+ var spec = qq.extend({}, this._filenameEditHandler());
+
+ return new qq.FilenameInputFocusHandler(spec);
+ },
+ _bindFilenameClickEvent: function() {
+ var spec = qq.extend({}, this._filenameEditHandler());
+
+ return new qq.FilenameClickHandler(spec);
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type) {
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name) {
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // The file item has been added to the DOM.
+ _onSubmitted: function(id) {
+ // If the edit filename feature is enabled, mark the filename element as "editable" and the associated edit icon
+ if (this._isEditFilenameEnabled()) {
+ var item = this.getItemByFileId(id),
+ qqFilenameDisplay = qq(this._find(item, 'file')),
+ qqEditFilenameIcon = qq(this._find(item, 'editNameIcon')),
+ editableClass = this._classes.editable;
+
+ qqFilenameDisplay.addClass(editableClass);
+ qqEditFilenameIcon.addClass(editableClass);
+
+ // If the focusin event is not supported, we must add a focus handler to the newly create edit filename text input
+ if (!this._focusinEventSupported) {
+ this._filenameInputFocusHandler.addHandler(this._find(item, 'editFilenameInput'));
+ }
+ }
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ var onSuccessCallback = qq.bind(this._onSubmitDeleteSuccess, this, id);
+
+ qq.FineUploaderBasic.prototype._onSubmitDelete.call(this, id, onSuccessCallback);
+ },
+ _onSubmitDeleteSuccess: function(id) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ if (this._options.display.prependFiles) {
+ this._prependItem(item);
+ }
+ else {
+ this._listElement.appendChild(item);
+ }
+ this._filesInBatchAddedToUi += 1;
+
+ if (this._options.display.fileSizeOnSubmit && qq.supportedFeatures.ajaxUploading) {
+ this._displayFileSize(id);
+ }
+ },
+ _prependItem: function(item) {
+ var parentEl = this._listElement,
+ beforeEl = parentEl.firstChild;
+
+ if (this._totalFilesInBatch > 1 && this._filesInBatchAddedToUi > 0) {
+ beforeEl = qq(parentEl).children()[this._filesInBatchAddedToUi - 1].nextSibling;
+
+ }
+
+ parentEl.insertBefore(item, beforeEl);
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ },
+ _fileOrBlobRejected: function(id, name) {
+ this._totalFilesInBatch -= 1;
+ qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this, arguments);
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ this._totalFilesInBatch = items.length;
+ this._filesInBatchAddedToUi = 0;
+ qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this, arguments);
+ }
+});
+;/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function (o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ mandatedParams: {},
+ successfulResponseCodes: {
+ "DELETE": [200, 202, 204],
+ "POST": [200, 204]
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function (str, level) {},
+ onSend: function (id) {},
+ onComplete: function (id, xhrOrXdr, isError) {},
+ onCancel: function (id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = options.method === 'GET' || options.method === 'DELETE';
+
+
+ // [Simple methods](http://www.w3.org/TR/cors/#simple-method)
+ // are defined by the W3C in the CORS spec as a list of methods that, in part,
+ // make a CORS request eligible to be exempt from preflighting.
+ function isSimpleMethod() {
+ return qq.indexOf(["GET", "POST", "HEAD"], options.method) >= 0;
+ }
+
+ // [Simple headers](http://www.w3.org/TR/cors/#simple-header)
+ // are defined by the W3C in the CORS spec as a list of headers that, in part,
+ // make a CORS request eligible to be exempt from preflighting.
+ function containsNonSimpleHeaders(headers) {
+ var containsNonSimple = false;
+
+ qq.each(containsNonSimple, function(idx, header) {
+ if (qq.indexOf(["Accept", "Accept-Language", "Content-Language", "Content-Type"], header) < 0) {
+ containsNonSimple = true;
+ return false;
+ }
+ });
+
+ return containsNonSimple;
+ }
+
+ function isXdr(xhr) {
+ //The `withCredentials` test is a commonly accepted way to determine if XHR supports CORS.
+ return options.cors.expected && xhr.withCredentials === undefined;
+ }
+
+ // Returns either a new `XMLHttpRequest` or `XDomainRequest` instance.
+ function getCorsAjaxTransport() {
+ var xhrOrXdr;
+
+ if (window.XMLHttpRequest) {
+ xhrOrXdr = new XMLHttpRequest();
+
+ if (xhrOrXdr.withCredentials === undefined) {
+ xhrOrXdr = new XDomainRequest();
+ }
+ }
+
+ return xhrOrXdr;
+ }
+
+ // Returns either a new XHR/XDR instance, or an existing one for the associated `File` or `Blob`.
+ function getXhrOrXdr(id, dontCreateIfNotExist) {
+ var xhrOrXdr = requestState[id].xhr;
+
+ if (!xhrOrXdr && !dontCreateIfNotExist) {
+ if (options.cors.expected) {
+ xhrOrXdr = getCorsAjaxTransport();
+ }
+ else {
+ xhrOrXdr = new XMLHttpRequest();
+ }
+
+ requestState[id].xhr = xhrOrXdr;
+ }
+
+ return xhrOrXdr;
+ }
+
+ // Removes element from queue, sends next request
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max) {
+ nextId = queue[max - 1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id, xdrError) {
+ var xhr = getXhrOrXdr(id),
+ method = options.method,
+ isError = xdrError === false;
+
+ dequeue(id);
+
+ if (isError) {
+ log(method + " request for " + id + " has failed", "error");
+ }
+ else if (!isXdr(xhr) && !isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function getParams(id) {
+ var params = {},
+ additionalParams = requestState[id].additionalParams,
+ mandatedParams = options.mandatedParams;
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ if (additionalParams) {
+ qq.each(additionalParams, function (name, val) {
+ params[name] = val;
+ });
+ }
+
+ if (mandatedParams) {
+ qq.each(mandatedParams, function (name, val) {
+ params[name] = val;
+ });
+ }
+
+ return params;
+ }
+
+ function sendRequest(id) {
+ var xhr = getXhrOrXdr(id),
+ method = options.method,
+ params = getParams(id),
+ url;
+
+ options.onSend(id);
+
+ url = createUrl(id, params);
+
+ // XDR and XHR status detection APIs differ a bit.
+ if (isXdr(xhr)) {
+ xhr.onload = getXdrLoadHandler(id);
+ xhr.onerror = getXdrErrorHandler(id);
+ }
+ else {
+ xhr.onreadystatechange = getXhrReadyStateChangeHandler(id);
+ }
+
+ // The last parameter is assumed to be ignored if we are actually using `XDomainRequest`.
+ xhr.open(method, url, true);
+
+ // Instruct the transport to send cookies along with the CORS request,
+ // unless we are using `XDomainRequest`, which is not capable of this.
+ if (options.cors.expected && options.cors.sendCredentials && !isXdr(xhr)) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath != undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ // Invoked by the UA to indicate a number of possible states that describe
+ // a live `XMLHttpRequest` transport.
+ function getXhrReadyStateChangeHandler(id) {
+ return function () {
+ if (getXhrOrXdr(id).readyState === 4) {
+ onComplete(id);
+ }
+ };
+ }
+
+ // This will be called by IE to indicate **success** for an associated
+ // `XDomainRequest` transported request.
+ function getXdrLoadHandler(id) {
+ return function () {
+ onComplete(id);
+ }
+ }
+
+ // This will be called by IE to indicate **failure** for an associated
+ // `XDomainRequest` transported request.
+ function getXdrErrorHandler(id) {
+ return function () {
+ onComplete(id, true);
+ }
+ }
+
+ function setHeaders(id) {
+ var xhr = getXhrOrXdr(id),
+ customHeaders = options.customHeaders;
+
+ // If this is a CORS request and a simple method with simple headers are used
+ // on an `XMLHttpRequest`, exclude these specific non-simple headers
+ // in an attempt to prevent preflighting. `XDomainRequest` does not support setting
+ // request headers, so we will take this into account as well.
+ if (isXdr(xhr)) {
+ if (!options.cors.expected || (!isSimpleMethod() || containsNonSimpleHeaders(customHeaders))) {
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+ }
+ }
+
+ // Assuming that all POST and PUT requests will need to be URL encoded.
+ // The payload of a POST `XDomainRequest` also needs to be URL encoded, but we
+ // can't set the Content-Type when using this transport.
+ if ((options.method === "POST" || options.method === "PUT") && !isXdr(xhr)) {
+ xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+ }
+
+ // `XDomainRequest` doesn't allow you to set any headers.
+ if (!isXdr(xhr)) {
+ qq.each(customHeaders, function (name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+ }
+
+ function cancelRequest(id) {
+ var xhr = getXhrOrXdr(id, true),
+ method = options.method;
+
+ if (xhr) {
+ // The event handlers we remove/unregister is dependant on whether we are
+ // using `XDomainRequest` or `XMLHttpRequest`.
+ if (isXdr(xhr)) {
+ xhr.onerror = null;
+ xhr.onload = null;
+ }
+ else {
+ xhr.onreadystatechange = null;
+ }
+
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes[options.method], responseCode) >= 0;
+ }
+
+ return {
+ send: function (id, addToPath, additionalParams) {
+ requestState[id] = {
+ addToPath: addToPath,
+ additionalParams: additionalParams
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections) {
+ sendRequest(id);
+ }
+ },
+ cancel: function (id) {
+ return cancelRequest(id);
+ }
+ };
+};
+;/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ validMethods = ["POST", "DELETE"],
+ options = {
+ method: "DELETE",
+ uuidParamName: "qquuid",
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhrOrXdr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ if (qq.indexOf(validMethods, getNormalizedMethod()) < 0) {
+ throw new Error("'" + getNormalizedMethod() + "' is not a supported method for delete file requests!");
+ }
+
+ function getNormalizedMethod() {
+ return options.method.toUpperCase();
+ }
+
+ function getMandatedParams() {
+ if (getNormalizedMethod() === "POST") {
+ return {
+ "_method": "DELETE"
+ };
+ }
+
+ return {};
+ }
+
+ requestor = new qq.AjaxRequestor({
+ method: getNormalizedMethod(),
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ mandatedParams: getMandatedParams(),
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete,
+ cors: options.cors
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ var additionalOptions = {};
+
+ options.log("Submitting delete file request for " + id);
+
+ if (getNormalizedMethod() === "DELETE") {
+ requestor.send(id, uuid);
+ }
+ else {
+ additionalOptions[options.uuidParamName] = uuid;
+ requestor.send(id, null, additionalOptions);
+ }
+ }
+ };
+};
+;qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+;/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, handlerImpl, api;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ filenameParam: 'qqfilename',
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){},
+ onUuidChanged: function(id, newUuid){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.supportedFeatures.ajaxUploading) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, options.onUuidChanged, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, options.onUuidChanged, log);
+ }
+
+ function cancelSuccess(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ dequeue(id);
+ }
+
+
+ api = {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ handlerImpl.upload(id);
+ return true;
+ }
+
+ return false;
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ var cancelRetVal = handlerImpl.cancel(id);
+
+ if (qq.isPromise(cancelRetVal)) {
+ cancelRetVal.then(function() {
+ cancelSuccess(id);
+ });
+ }
+ else if (cancelRetVal !== false) {
+ cancelSuccess(id);
+ }
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id) {
+ return handlerImpl.getName(id);
+ },
+ // Update/change the name of the associated file.
+ // This updated name should be sent as a parameter.
+ setName: function(id, newName) {
+ handlerImpl.setName(id, newName);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ api.cancelAll();
+ queue = [];
+ handlerImpl.reset();
+ },
+ expunge: function(id) {
+ return handlerImpl.expunge(id);
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+;/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, onUuidChanged, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ newNames = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ formHandlerInstanceId = qq.getUniqueId(),
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var iframeName = iframe.id,
+ fileId = getFileIdForIframeName(iframeName);
+
+ onloadCallbacks[uuids[fileId]] = callback;
+
+ detachLoadEvents[fileId] = qq(iframe).attach('load', function() {
+ if (inputs[fileId]) {
+ log("Received iframe load event for CORS upload request (iframe name " + iframeName + ")");
+
+ postMessageCallbackTimers[iframeName] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for iframe name " + iframeName;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(iframeName, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = parseResponse(getFileIdForIframeName(iframeName), message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ log("Handling response for iframe name " + iframeName);
+ clearTimeout(postMessageCallbackTimers[iframeName]);
+ delete postMessageCallbackTimers[iframeName];
+
+ detachLoadEvent(iframeName);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(iframeName);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(id, iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHtml = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHtml);
+ //plain text response may be wrapped in tag
+ if (innerHtml && innerHtml.match(/^ ');
+
+ iframe.setAttribute('id', iframeName);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (newNames[id] !== undefined) {
+ params[options.filenameParam] = newNames[id];
+ }
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+ function expungeFile(id) {
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(getIframeName(id));
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ }
+
+ function getFileIdForIframeName(iframeName) {
+ return iframeName.split("_")[0];
+ }
+
+ function getIframeName(fileId) {
+ return fileId + "_" + formHandlerInstanceId;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (newNames[id] !== undefined) {
+ return newNames[id];
+ }
+ else if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ setName: function(id, newName) {
+ newNames[id] = newName;
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ newNames = [];
+ detachLoadEvents = {};
+ formHandlerInstanceId = qq.getUniqueId();
+ },
+ expunge: function(id) {
+ return expungeFile(id);
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, api.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeFile(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeFile(id);
+ return true;
+ }
+
+ return false;
+ },
+
+ upload: function(id) {
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, api.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(id, iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+ }
+ };
+
+ return api;
+};
+;/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, onUuidChanged, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.supportedFeatures.chunking,
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.supportedFeatures.resume,
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.filenameParam] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData,
+ newName = fileState[id].newName;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.filenameParam] = blobData.name;
+ }
+ }
+
+ if (newName !== undefined) {
+ params[options.filenameParam] = newName;
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = newName || name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+ options.onComplete(id, name, response, xhr);
+
+ if (fileState[id]) {
+ delete fileState[id].xhr;
+ }
+
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(id, xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+
+ if (response.newUuid !== undefined) {
+ log("Server requested UUID change from '" + fileState[id].uuid + "' to '" + response.newUuid + "'");
+ fileState[id].uuid = response.newUuid;
+ onUuidChanged(id, response.newUuid);
+ }
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(id, xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex) {
+ var currentChunkIndex;
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+
+ function handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex) {
+ var name = api.getName(id),
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part),
+ onResumeRetVal;
+
+ onResumeRetVal = options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume));
+ if (qq.isPromise(onResumeRetVal)) {
+ log("Waiting for onResume promise to be fulfilled for " + id);
+ onResumeRetVal.then(
+ function() {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ },
+ function() {
+ log("onResume promise fulfilled - failure indicated. Will not resume.")
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ );
+ }
+ else if (onResumeRetVal !== false) {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ }
+ else {
+ log("onResume callback returned false. Will not resume.");
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var firstChunkIndex = 0,
+ persistedChunkInfoForResume;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex);
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ uploadNextChunk(id);
+ }
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+ function expungeItem(id) {
+ var xhr = fileState[id].xhr;
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ }
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id, persistedChunkData,
+ uuid = qq.getUniqueId();
+
+ if (qq.isFile(fileOrBlobData)) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (qq.isBlob(fileOrBlobData.blob)) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ if (resumeEnabled) {
+ persistedChunkData = getPersistedChunkData(id);
+
+ if (persistedChunkData) {
+ uuid = persistedChunkData.uuid;
+ }
+ }
+
+ fileState[id].uuid = uuid;
+
+ return id;
+ },
+ getName: function(id) {
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData,
+ newName = fileState[id].newName;
+
+ if (newName !== undefined) {
+ return newName;
+ }
+ else if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ setName: function(id, newName) {
+ fileState[id].newName = newName;
+ },
+ getSize: function(id) {
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ expunge: function(id) {
+ return expungeItem(id);
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry) {
+ var name = this.getName(id);
+
+ if (this.isValid(id)) {
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ }
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, this.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeItem(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeItem(id);
+ return true;
+ }
+
+ return false;
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+;// Base handler for UI (FineUploader mode) events.
+// Some more specific handlers inherit from this one.
+qq.UiEventHandler = function(s, protectedApi) {
+ "use strict";
+
+ var disposer = new qq.DisposeSupport(),
+ spec = {
+ eventType: 'click',
+ attachTo: null,
+ onHandled: function(target, event) {}
+ },
+ // This makes up the "public" API methods that will be accessible
+ // to instances constructing a base or child handler
+ publicApi = {
+ addHandler: function(element) {
+ addHandler(element);
+ },
+
+ dispose: function() {
+ disposer.dispose();
+ }
+ };
+
+
+
+ function addHandler(element) {
+ disposer.attach(element, spec.eventType, function(event) {
+ // Only in IE: the `event` is a property of the `window`.
+ event = event || window.event;
+
+ // On older browsers, we must check the `srcElement` instead of the `target`.
+ var target = event.target || event.srcElement;
+
+ spec.onHandled(target, event);
+ });
+ }
+
+ // These make up the "protected" API methods that children of this base handler will utilize.
+ qq.extend(protectedApi, {
+ // Find the ID of the associated file by looking for an
+ // expando property present on each file item in the DOM.
+ getItemFromEventTarget: function(target) {
+ var item = target.parentNode;
+
+ while(item.qqFileId === undefined) {
+ item = item.parentNode;
+ }
+
+ return item;
+ },
+
+ getFileIdFromItem: function(item) {
+ return item.qqFileId;
+ },
+
+ getDisposeSupport: function() {
+ return disposer;
+ }
+ });
+
+
+ qq.extend(spec, s);
+
+ if (spec.attachTo) {
+ addHandler(spec.attachTo);
+ }
+
+ return publicApi;
+};
+;qq.DeleteRetryOrCancelClickHandler = function(s) {
+ "use strict";
+
+ var inheritedInternalApi = {},
+ spec = {
+ listElement: document,
+ log: function(message, lvl) {},
+ classes: {
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry'
+ },
+ onDeleteFile: function(fileId) {},
+ onCancel: function(fileId) {},
+ onRetry: function(fileId) {},
+ onGetName: function(fileId) {}
+ };
+
+ function examineEvent(target, event) {
+ if (qq(target).hasClass(spec.classes.cancel)
+ || qq(target).hasClass(spec.classes.retry)
+ || qq(target).hasClass(spec.classes.deleteButton)) {
+
+ var item = inheritedInternalApi.getItemFromEventTarget(target),
+ fileId = inheritedInternalApi.getFileIdFromItem(item);
+
+ qq.preventDefault(event);
+
+ spec.log(qq.format("Detected valid cancel, retry, or delete click event on file '{}', ID: {}.", spec.onGetName(fileId), fileId));
+ deleteRetryOrCancel(target, fileId);
+ }
+ }
+
+ function deleteRetryOrCancel(target, fileId) {
+ if (qq(target).hasClass(spec.classes.deleteButton)) {
+ spec.onDeleteFile(fileId);
+ }
+ else if (qq(target).hasClass(spec.classes.cancel)) {
+ spec.onCancel(fileId);
+ }
+ else {
+ spec.onRetry(fileId);
+ }
+ }
+
+ qq.extend(spec, s);
+
+ spec.eventType = 'click';
+ spec.onHandled = examineEvent;
+ spec.attachTo = spec.listElement;
+
+ qq.extend(this, new qq.UiEventHandler(spec, inheritedInternalApi));
+};
+;// Handles edit-related events on a file item (FineUploader mode). This is meant to be a parent handler.
+// Children will delegate to this handler when specific edit-related actions are detected.
+qq.FilenameEditHandler = function(s, inheritedInternalApi) {
+ "use strict";
+
+ var spec = {
+ listElement: null,
+ log: function(message, lvl) {},
+ classes: {
+ file: 'qq-upload-file'
+ },
+ onGetUploadStatus: function(fileId) {},
+ onGetName: function(fileId) {},
+ onSetName: function(fileId, newName) {},
+ onGetInput: function(item) {},
+ onEditingStatusChange: function(fileId, isEditing) {}
+ },
+ publicApi;
+
+ function getFilenameSansExtension(fileId) {
+ var filenameSansExt = spec.onGetName(fileId),
+ extIdx = filenameSansExt.lastIndexOf('.');
+
+ if (extIdx > 0) {
+ filenameSansExt = filenameSansExt.substr(0, extIdx);
+ }
+
+ return filenameSansExt;
+ }
+
+ function getOriginalExtension(fileId) {
+ var origName = spec.onGetName(fileId),
+ extIdx = origName.lastIndexOf('.');
+
+ if (extIdx > 0) {
+ return origName.substr(extIdx, origName.length - extIdx);
+ }
+ }
+
+ // Callback iff the name has been changed
+ function handleNameUpdate(newFilenameInputEl, fileId) {
+ var newName = newFilenameInputEl.value,
+ origExtension;
+
+ if (newName !== undefined && qq.trimStr(newName).length > 0) {
+ origExtension = getOriginalExtension(fileId);
+
+ if (origExtension !== undefined) {
+ newName = newName + getOriginalExtension(fileId);
+ }
+
+ spec.onSetName(fileId, newName);
+ }
+
+ spec.onEditingStatusChange(fileId, false);
+ }
+
+ // The name has been updated if the filename edit input loses focus.
+ function registerInputBlurHandler(inputEl, fileId) {
+ inheritedInternalApi.getDisposeSupport().attach(inputEl, 'blur', function() {
+ handleNameUpdate(inputEl, fileId)
+ });
+ }
+
+ // The name has been updated if the user presses enter.
+ function registerInputEnterKeyHandler(inputEl, fileId) {
+ inheritedInternalApi.getDisposeSupport().attach(inputEl, 'keyup', function(event) {
+
+ var code = event.keyCode || event.which;
+
+ if (code === 13) {
+ handleNameUpdate(inputEl, fileId)
+ }
+ });
+ }
+
+ qq.extend(spec, s);
+
+ spec.attachTo = spec.listElement;
+
+ publicApi = qq.extend(this, new qq.UiEventHandler(spec, inheritedInternalApi));
+
+ qq.extend(inheritedInternalApi, {
+ handleFilenameEdit: function(fileId, target, item, focusInput) {
+ var newFilenameInputEl = spec.onGetInput(item);
+
+ spec.onEditingStatusChange(fileId, true);
+
+ newFilenameInputEl.value = getFilenameSansExtension(fileId);
+
+ if (focusInput) {
+ newFilenameInputEl.focus();
+ }
+
+ registerInputBlurHandler(newFilenameInputEl, fileId);
+ registerInputEnterKeyHandler(newFilenameInputEl, fileId);
+ }
+ });
+
+ return publicApi;
+};
+;// Child of FilenameEditHandler. Used to detect click events on filename display elements.
+qq.FilenameClickHandler = function(s) {
+ "use strict";
+
+ var inheritedInternalApi = {},
+ spec = {
+ log: function(message, lvl) {},
+ classes: {
+ file: 'qq-upload-file',
+ editNameIcon: 'qq-edit-filename-icon'
+ },
+ onGetUploadStatus: function(fileId) {},
+ onGetName: function(fileId) {}
+ };
+
+ qq.extend(spec, s);
+
+ // This will be called by the parent handler when a `click` event is received on the list element.
+ function examineEvent(target, event) {
+ if (qq(target).hasClass(spec.classes.file) || qq(target).hasClass(spec.classes.editNameIcon)) {
+ var item = inheritedInternalApi.getItemFromEventTarget(target),
+ fileId = inheritedInternalApi.getFileIdFromItem(item),
+ status = spec.onGetUploadStatus(fileId);
+
+ // We only allow users to change filenames of files that have been submitted but not yet uploaded.
+ if (status === qq.status.SUBMITTED) {
+ spec.log(qq.format("Detected valid filename click event on file '{}', ID: {}.", spec.onGetName(fileId), fileId));
+ qq.preventDefault(event);
+
+ inheritedInternalApi.handleFilenameEdit(fileId, target, item, true);
+ }
+ }
+ }
+
+ spec.eventType = 'click';
+ spec.onHandled = examineEvent;
+
+ return qq.extend(this, new qq.FilenameEditHandler(spec, inheritedInternalApi));
+};
+;// Child of FilenameEditHandler. Used to detect focusin events on file edit input elements.
+qq.FilenameInputFocusInHandler = function(s, inheritedInternalApi) {
+ "use strict";
+
+ var spec = {
+ listElement: null,
+ classes: {
+ editFilenameInput: 'qq-edit-filename'
+ },
+ onGetUploadStatus: function(fileId) {},
+ log: function(message, lvl) {}
+ };
+
+ if (!inheritedInternalApi) {
+ inheritedInternalApi = {};
+ }
+
+ // This will be called by the parent handler when a `focusin` event is received on the list element.
+ function handleInputFocus(target, event) {
+ if (qq(target).hasClass(spec.classes.editFilenameInput)) {
+ var item = inheritedInternalApi.getItemFromEventTarget(target),
+ fileId = inheritedInternalApi.getFileIdFromItem(item),
+ status = spec.onGetUploadStatus(fileId);
+
+ if (status === qq.status.SUBMITTED) {
+ spec.log(qq.format("Detected valid filename input focus event on file '{}', ID: {}.", spec.onGetName(fileId), fileId));
+ inheritedInternalApi.handleFilenameEdit(fileId, target, item);
+ }
+ }
+ }
+
+ spec.eventType = 'focusin';
+ spec.onHandled = handleInputFocus;
+
+ qq.extend(spec, s);
+
+ return qq.extend(this, new qq.FilenameEditHandler(spec, inheritedInternalApi));
+};
+;/**
+ * Child of FilenameInputFocusInHandler. Used to detect focus events on file edit input elements. This child module is only
+ * needed for UAs that do not support the focusin event. Currently, only Firefox lacks this event.
+ *
+ * @param spec Overrides for default specifications
+ */
+qq.FilenameInputFocusHandler = function(spec) {
+ "use strict";
+
+ spec.eventType = 'focus';
+ spec.attachTo = null;
+
+ return qq.extend(this, new qq.FilenameInputFocusInHandler(spec, {}));
+};
+;/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var uploader, $el, init, dataStore, pluginOption, pluginOptions, addCallbacks, transformVariables, isValidCommand,
+ delegateCommand;
+
+ pluginOptions = ['uploaderType'];
+
+ init = function (options) {
+ if (options) {
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+
+ if (pluginOption('uploaderType') === 'basic') {
+ uploader(new qq.FineUploaderBasic(xformedOpts));
+ }
+ else {
+ uploader(new qq.FineUploader(xformedOpts));
+ }
+ }
+
+ return $el;
+ };
+
+ dataStore = function(key, val) {
+ var data = $el.data('fineuploader');
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data('fineuploader', data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ //the underlying Fine Uploader instance is stored in jQuery's data stored, associated with the element
+ // tied to this instance of the plug-in
+ uploader = function(instanceToStore) {
+ return dataStore('uploader', instanceToStore);
+ };
+
+ pluginOption = function(option, optionVal) {
+ return dataStore(option, optionVal);
+ };
+
+ //implement all callbacks defined in Fine Uploader as functions that trigger appropriately names events and
+ // return the result of executing the bound handler back to Fine Uploader
+ addCallbacks = function(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {},
+ uploaderInst = new qq.FineUploaderBasic();
+
+ $.each(uploaderInst._options.callbacks, function(prop, func) {
+ var name, $callbackEl;
+
+ name = /^on(\w+)/.exec(prop)[1];
+ name = name.substring(0, 1).toLowerCase() + name.substring(1);
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var args = Array.prototype.slice.call(arguments);
+
+ return $callbackEl.triggerHandler(name, args);
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ transformVariables = function(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ if (source.uploaderType !== 'basic') {
+ xformed = { element : $el[0] };
+ }
+ else {
+ xformed = {};
+ }
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if ($.inArray(prop, pluginOptions) >= 0) {
+ pluginOption(prop, val);
+ }
+ else if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ isValidCommand = function(command) {
+ return $.type(command) === "string" &&
+ !command.match(/^_/) && //enforce private methods convention
+ uploader()[command] !== undefined;
+ };
+
+ //assuming we have already verified that this is a valid command, call the associated function in the underlying
+ // Fine Uploader instance (passing along the arguments from the caller) and return the result of the call back to the caller
+ delegateCommand = function(command) {
+ var xformedArgs = [],
+ origArgs = Array.prototype.slice.call(arguments, 1),
+ retVal;
+
+ transformVariables(origArgs, xformedArgs);
+
+ retVal = uploader()[command].apply(uploader(), xformedArgs);
+
+ // If the command is returning an `HTMLElement` or `HTMLDocument`, wrap it in a `jQuery` object
+ if(typeof retVal === "object"
+ && (retVal.nodeType === 1 || retVal.nodeType === 9)
+ && retVal.cloneNode) {
+
+ retVal = $(retVal);
+ }
+
+ return retVal;
+ };
+
+ $.fn.fineUploader = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (uploader() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error('Method ' + optionsOrCommand + ' does not exist on jQuery.fineUploader');
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
+;/*globals jQuery, qq*/
+(function($) {
+ "use strict";
+ var rootDataKey = "fineUploaderDnd",
+ $el;
+
+ function init (options) {
+ if (!options) {
+ options = {};
+ }
+
+ options.dropZoneElements = [$el];
+ var xformedOpts = transformVariables(options);
+ addCallbacks(xformedOpts);
+ dnd(new qq.DragAndDrop(xformedOpts));
+
+ return $el;
+ };
+
+ function dataStore(key, val) {
+ var data = $el.data(rootDataKey);
+
+ if (val) {
+ if (data === undefined) {
+ data = {};
+ }
+ data[key] = val;
+ $el.data(rootDataKey, data);
+ }
+ else {
+ if (data === undefined) {
+ return null;
+ }
+ return data[key];
+ }
+ };
+
+ function dnd(instanceToStore) {
+ return dataStore('dndInstance', instanceToStore);
+ };
+
+ function addCallbacks(transformedOpts) {
+ var callbacks = transformedOpts.callbacks = {},
+ dndInst = new qq.FineUploaderBasic();
+
+ $.each(new qq.DragAndDrop.callbacks(), function(prop, func) {
+ var name = prop,
+ $callbackEl;
+
+ $callbackEl = $el;
+
+ callbacks[prop] = function() {
+ var args = Array.prototype.slice.call(arguments),
+ jqueryHandlerResult = $callbackEl.triggerHandler(name, args);
+
+ return jqueryHandlerResult;
+ };
+ });
+ };
+
+ //transform jQuery objects into HTMLElements, and pass along all other option properties
+ function transformVariables(source, dest) {
+ var xformed, arrayVals;
+
+ if (dest === undefined) {
+ xformed = {};
+ }
+ else {
+ xformed = dest;
+ }
+
+ $.each(source, function(prop, val) {
+ if (val instanceof $) {
+ xformed[prop] = val[0];
+ }
+ else if ($.isPlainObject(val)) {
+ xformed[prop] = {};
+ transformVariables(val, xformed[prop]);
+ }
+ else if ($.isArray(val)) {
+ arrayVals = [];
+ $.each(val, function(idx, arrayVal) {
+ if (arrayVal instanceof $) {
+ $.merge(arrayVals, arrayVal);
+ }
+ else {
+ arrayVals.push(arrayVal);
+ }
+ });
+ xformed[prop] = arrayVals;
+ }
+ else {
+ xformed[prop] = val;
+ }
+ });
+
+ if (dest === undefined) {
+ return xformed;
+ }
+ };
+
+ function isValidCommand(command) {
+ return $.type(command) === "string" &&
+ command === "dispose" &&
+ dnd()[command] !== undefined;
+ };
+
+ function delegateCommand(command) {
+ var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);
+ transformVariables(origArgs, xformedArgs);
+ return dnd()[command].apply(dnd(), xformedArgs);
+ };
+
+ $.fn.fineUploaderDnd = function(optionsOrCommand) {
+ var self = this, selfArgs = arguments, retVals = [];
+
+ this.each(function(index, el) {
+ $el = $(el);
+
+ if (dnd() && isValidCommand(optionsOrCommand)) {
+ retVals.push(delegateCommand.apply(self, selfArgs));
+
+ if (self.length === 1) {
+ return false;
+ }
+ }
+ else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
+ init.apply(self, selfArgs);
+ }
+ else {
+ $.error("Method " + optionsOrCommand + " does not exist in Fine Uploader's DnD module.");
+ }
+ });
+
+ if (retVals.length === 1) {
+ return retVals[0];
+ }
+ else if (retVals.length > 1) {
+ return retVals;
+ }
+
+ return this;
+ };
+
+}(jQuery));
+
+/*! 2013-07-16 */
diff --git a/ajax/libs/file-uploader/3.7.0/fineuploader-jquery.min.js b/ajax/libs/file-uploader/3.7.0/fineuploader-jquery.min.js
new file mode 100644
index 000000000..9ed18be2b
--- /dev/null
+++ b/ajax/libs/file-uploader/3.7.0/fineuploader-jquery.min.js
@@ -0,0 +1,19 @@
+/*!
+ * Fine Uploader
+ *
+ * Copyright 2013, Widen Enterprises, Inc. info@fineuploader.com
+ *
+ * Version: 3.7.0
+ *
+ * Homepage: http://fineuploader.com
+ *
+ * Repository: git://github.com/Widen/fine-uploader.git
+ *
+ * Licensed under GNU GPL v3, see LICENSE
+ */
+
+
+var qq=function(a){"use strict";return{hide:function(){return a.style.display="none",this},attach:function(b,c){return a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent&&a.attachEvent("on"+b,c),function(){qq(a).detach(b,c)}},detach:function(b,c){return a.removeEventListener?a.removeEventListener(b,c,!1):a.attachEvent&&a.detachEvent("on"+b,c),this},contains:function(b){return b?a===b?!0:a.contains?a.contains(b):!!(8&b.compareDocumentPosition(a)):!1},insertBefore:function(b){return b.parentNode.insertBefore(a,b),this},remove:function(){return a.parentNode.removeChild(a),this},css:function(b){return null!=b.opacity&&"string"!=typeof a.style.opacity&&"undefined"!=typeof a.filters&&(b.filter="alpha(opacity="+Math.round(100*b.opacity)+")"),qq.extend(a.style,b),this},hasClass:function(b){var c=new RegExp("(^| )"+b+"( |$)");return c.test(a.className)},addClass:function(b){return qq(a).hasClass(b)||(a.className+=" "+b),this},removeClass:function(b){var c=new RegExp("(^| )"+b+"( |$)");return a.className=a.className.replace(c," ").replace(/^\s+|\s+$/g,""),this},getByClass:function(b){var c,d=[];return a.querySelectorAll?a.querySelectorAll("."+b):(c=a.getElementsByTagName("*"),qq.each(c,function(a,c){qq(c).hasClass(b)&&d.push(c)}),d)},children:function(){for(var b=[],c=a.firstChild;c;)1===c.nodeType&&b.push(c),c=c.nextSibling;return b},setText:function(b){return a.innerText=b,a.textContent=b,this},clearText:function(){return qq(a).setText("")}}};qq.log=function(a,b){"use strict";window.console&&(b&&"info"!==b?window.console[b]?window.console[b](a):window.console.log("<"+b+"> "+a):window.console.log(a))},qq.isObject=function(a){"use strict";return a&&!a.nodeType&&"[object Object]"===Object.prototype.toString.call(a)},qq.isFunction=function(a){"use strict";return"function"==typeof a},qq.isArray=function(a){"use strict";return"[object Array]"===Object.prototype.toString.call(a)},qq.isString=function(a){"use strict";return"[object String]"===Object.prototype.toString.call(a)},qq.trimStr=function(a){return String.prototype.trim?a.trim():a.replace(/^\s+|\s+$/g,"")},qq.format=function(a){"use strict";var b=Array.prototype.slice.call(arguments,1),c=a;return qq.each(b,function(a,b){c=c.replace(/{}/,b)}),c},qq.isFile=function(a){"use strict";return window.File&&"[object File]"===Object.prototype.toString.call(a)},qq.isFileList=function(a){return window.FileList&&"[object FileList]"===Object.prototype.toString.call(a)},qq.isFileOrInput=function(a){"use strict";return qq.isFile(a)||qq.isInput(a)},qq.isInput=function(a){return window.HTMLInputElement&&"[object HTMLInputElement]"===Object.prototype.toString.call(a)&&a.type&&"file"===a.type.toLowerCase()?!0:a.tagName&&"input"===a.tagName.toLowerCase()&&a.type&&"file"===a.type.toLowerCase()?!0:!1},qq.isBlob=function(a){"use strict";return window.Blob&&"[object Blob]"===Object.prototype.toString.call(a)},qq.isXhrUploadSupported=function(){"use strict";var a=document.createElement("input");return a.type="file",void 0!==a.multiple&&"undefined"!=typeof File&&"undefined"!=typeof FormData&&"undefined"!=typeof(new XMLHttpRequest).upload},qq.isFolderDropSupported=function(a){"use strict";return a.items&&a.items[0].webkitGetAsEntry},qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(void 0!==File.prototype.slice||void 0!==File.prototype.webkitSlice||void 0!==File.prototype.mozSlice)},qq.extend=function(a,b,c){"use strict";return qq.each(b,function(b,d){c&&qq.isObject(d)?(void 0===a[b]&&(a[b]={}),qq.extend(a[b],d,!0)):a[b]=d}),a},qq.indexOf=function(a,b,c){"use strict";if(a.indexOf)return a.indexOf(b,c);c=c||0;var d=a.length;for(0>c&&(c+=d);d>c;c+=1)if(a.hasOwnProperty(c)&&a[c]===b)return c;return-1},qq.getUniqueId=function(){"use strict";return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(a){var b=0|16*Math.random(),c="x"==a?b:8|3&b;return c.toString(16)})},qq.ie=function(){"use strict";return-1!==navigator.userAgent.indexOf("MSIE")},qq.ie10=function(){"use strict";return-1!==navigator.userAgent.indexOf("MSIE 10")},qq.safari=function(){"use strict";return void 0!==navigator.vendor&&-1!==navigator.vendor.indexOf("Apple")},qq.chrome=function(){"use strict";return void 0!==navigator.vendor&&-1!==navigator.vendor.indexOf("Google")},qq.firefox=function(){"use strict";return-1!==navigator.userAgent.indexOf("Mozilla")&&void 0!==navigator.vendor&&""===navigator.vendor},qq.windows=function(){"use strict";return"Win32"===navigator.platform},qq.android=function(){"use strict";return-1!==navigator.userAgent.toLowerCase().indexOf("android")},qq.ios=function(){"use strict";return-1!==navigator.userAgent.indexOf("iPad")||-1!==navigator.userAgent.indexOf("iPod")||-1!==navigator.userAgent.indexOf("iPhone")},qq.preventDefault=function(a){"use strict";a.preventDefault?a.preventDefault():a.returnValue=!1},qq.toElement=function(){"use strict";var a=document.createElement("div");return function(b){a.innerHTML=b;var c=a.firstChild;return a.removeChild(c),c}}(),qq.each=function(a,b){"use strict";var c,d;if(a)if(qq.isArray(a))for(c=0;cd;d+=1)h(a[d],d);else if("undefined"!=typeof a&&null!==a&&"object"==typeof a)for(d in a)a.hasOwnProperty(d)&&h(a[d],d);else f.push(encodeURIComponent(b)+"="+encodeURIComponent(a));return b?f.join(g):f.join(g).replace(/^&/,"").replace(/%20/g,"+")},qq.obj2FormData=function(a,b,c){"use strict";return b||(b=new FormData),qq.each(a,function(a,d){a=c?c+"["+a+"]":a,qq.isObject(d)?qq.obj2FormData(d,b,a):qq.isFunction(d)?b.append(a,d()):b.append(a,d)}),b},qq.obj2Inputs=function(a,b){"use strict";var c;return b||(b=document.createElement("form")),qq.obj2FormData(a,{append:function(a,d){c=document.createElement("input"),c.setAttribute("name",a),c.setAttribute("value",d),b.appendChild(c)}}),b},qq.setCookie=function(a,b,c){var d=new Date,e="";c&&(d.setTime(d.getTime()+1e3*60*60*24*c),e="; expires="+d.toGMTString()),document.cookie=a+"="+b+e+"; path=/"},qq.getCookie=function(a){var b,c=a+"=",d=document.cookie.split(";");return qq.each(d,function(a,d){for(var e=d;" "==e.charAt(0);)e=e.substring(1,e.length);return 0===e.indexOf(c)?(b=e.substring(c.length,e.length),!1):void 0}),b},qq.getCookieNames=function(a){var b=document.cookie.split(";"),c=[];return qq.each(b,function(b,d){d=qq.trimStr(d);var e=d.indexOf("=");d.match(a)&&c.push(d.substr(0,e))}),c},qq.deleteCookie=function(a){qq.setCookie(a,"",-1)},qq.areCookiesEnabled=function(){var a=1e5*Math.random(),b="qqCookieTest:"+a;return qq.setCookie(b,1),qq.getCookie(b)?(qq.deleteCookie(b),!0):!1},qq.parseJson=function(json){return window.JSON&&qq.isFunction(JSON.parse)?JSON.parse(json):eval("("+json+")")},qq.DisposeSupport=function(){"use strict";var a=[];return{dispose:function(){var b;do b=a.shift(),b&&b();while(b)},attach:function(){var a=arguments;this.addDisposer(qq(a[0]).attach.apply(this,Array.prototype.slice.call(arguments,1)))},addDisposer:function(b){a.push(b)}}},qq.version="3.7.0",qq.supportedFeatures=function(){function a(){var a,b=!0;try{a=document.createElement("input"),a.type="file",qq(a).hide(),a.disabled&&(b=!1)}catch(c){b=!1}return b}function b(){return qq.chrome()&&void 0!==navigator.userAgent.match(/Chrome\/[2][1-9]|Chrome\/[3-9][0-9]/)}function c(){return qq.chrome()&&void 0!==navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/)}function d(){if(window.XMLHttpRequest){var a=new XMLHttpRequest;return void 0!==a.withCredentials}return!1}function e(){return void 0!==window.XDomainRequest}function f(){return d()?!0:e()}var g,h,i,j,k,l,m,n,o,p;return g=a(),h=g&&qq.isXhrUploadSupported(),i=h&&b(),j=h&&qq.isFileChunkingSupported(),k=h&&j&&qq.areCookiesEnabled(),l=h&&c(),m=g&&(void 0!==window.postMessage||h),o=d(),n=e(),p=f(),{uploading:g,ajaxUploading:h,fileDrop:h,folderDrop:i,chunking:j,resume:k,uploadCustomHeaders:h,uploadNonMultipart:h,itemSizeValidation:h,uploadViaPaste:l,progressBar:h,uploadCors:m,deleteFileCorsXhr:o,deleteFileCorsXdr:n,deleteFileCors:p,canDetermineSize:h}}(),qq.Promise=function(){"use strict";var a,b,c=[],d=[],e=[],f=0;return{then:function(e,g){return 0===f?(e&&c.push(e),g&&d.push(g)):-1===f&&g?g(b):e&&e(a),this},done:function(a){return 0===f?e.push(a):a(),this},success:function(b){return f=1,a=b,c.length&&qq.each(c,function(a,c){c(b)}),e.length&&qq.each(e,function(a,b){b()}),this},failure:function(a){return f=-1,b=a,d.length&&qq.each(d,function(b,c){c(a)}),e.length&&qq.each(e,function(a,b){b()}),this}}},qq.isPromise=function(a){return a&&a.then&&a.done},qq.UploadButton=function(a){"use strict";function b(){var a=document.createElement("input");return e.multiple&&a.setAttribute("multiple","multiple"),e.acceptFiles&&a.setAttribute("accept",e.acceptFiles),a.setAttribute("type","file"),a.setAttribute("name",e.name),qq(a).css({position:"absolute",right:0,top:0,fontFamily:"Arial",fontSize:"118px",margin:0,padding:0,cursor:"pointer",opacity:0}),e.element.appendChild(a),d.attach(a,"change",function(){e.onChange(a)}),d.attach(a,"mouseover",function(){qq(e.element).addClass(e.hoverClass)}),d.attach(a,"mouseout",function(){qq(e.element).removeClass(e.hoverClass)}),d.attach(a,"focus",function(){qq(e.element).addClass(e.focusClass)}),d.attach(a,"blur",function(){qq(e.element).removeClass(e.focusClass)}),window.attachEvent&&a.setAttribute("tabIndex","-1"),a}var c,d=new qq.DisposeSupport,e={element:null,multiple:!1,acceptFiles:null,name:"qqfile",onChange:function(){},hoverClass:"qq-upload-button-hover",focusClass:"qq-upload-button-focus"};return qq.extend(e,a),qq(e.element).css({position:"relative",overflow:"hidden",direction:"ltr"}),c=b(),{getInput:function(){return c},reset:function(){c.parentNode&&qq(c).remove(),qq(e.element).removeClass(e.focusClass),c=b()}}},qq.PasteSupport=function(a){"use strict";function b(a){return a.type&&0===a.type.indexOf("image/")}function c(){qq(e.targetElement).attach("paste",function(a){var c=a.clipboardData;c&&qq.each(c.items,function(a,c){if(b(c)){var d=c.getAsFile();e.callbacks.pasteReceived(d)}})})}function d(){f&&f()}var e,f;return e={targetElement:null,callbacks:{log:function(){},pasteReceived:function(){}}},qq.extend(e,a),c(),{reset:function(){d()}}},qq.UploadData=function(a){function b(a){if(qq.isArray(a)){var b=[];return qq.each(a,function(a,c){b.push(f[g[c]])}),b}return f[g[a]]}function c(a){if(qq.isArray(a)){var b=[];return qq.each(a,function(a,c){b.push(f[h[c]])}),b}return f[h[a]]}function d(a){var b=[],c=[].concat(a);return qq.each(c,function(a,c){var d=i[c];void 0!==d&&qq.each(d,function(a,c){b.push(f[c])})}),b}var e,f=[],g={},h={},i={};return e={added:function(b){var c=a.getUuid(b),d=a.getName(b),e=a.getSize(b),j=qq.status.SUBMITTING,k=f.push({id:b,name:d,originalName:d,uuid:c,size:e,status:j})-1;g[b]=k,h[c]=k,void 0===i[j]&&(i[j]=[]),i[j].push(k),a.onStatusChange(b,void 0,j)},retrieve:function(a){return qq.isObject(a)&&f.length?void 0!==a.id?b(a.id):void 0!==a.uuid?c(a.uuid):a.status?d(a.status):void 0:qq.extend([],f,!0)},reset:function(){f=[],g={},h={},i={}},setStatus:function(b,c){var d=g[b],e=f[d].status,h=qq.indexOf(i[e],d);i[e].splice(h,1),f[d].status=c,void 0===i[c]&&(i[c]=[]),i[c].push(d),a.onStatusChange(b,e,c)},uuidChanged:function(a,b){var c=g[a],d=f[c].uuid;f[c].uuid=b,h[b]=c,delete h[d]},nameChanged:function(a,b){var c=g[a];f[c].name=b}}},qq.status={SUBMITTING:"submitting",SUBMITTED:"submitted",REJECTED:"rejected",QUEUED:"queued",CANCELED:"canceled",UPLOADING:"uploading",UPLOAD_RETRYING:"retrying upload",UPLOAD_SUCCESSFUL:"upload successful",UPLOAD_FAILED:"upload failed",DELETE_FAILED:"delete failed",DELETING:"deleting",DELETED:"deleted"},qq.FineUploaderBasic=function(a){this._options={debug:!1,button:null,multiple:!0,maxConnections:3,disableCancelForFormUploads:!1,autoUpload:!0,request:{endpoint:"/server/upload",params:{},paramsInBody:!0,customHeaders:{},forceMultipart:!0,inputName:"qqfile",uuidName:"qquuid",totalFileSizeName:"qqtotalfilesize",filenameParam:"qqfilename"},validation:{allowedExtensions:[],sizeLimit:0,minSizeLimit:0,itemLimit:0,stopOnFirstInvalidFile:!0,acceptFiles:null},callbacks:{onSubmit:function(){},onSubmitted:function(){},onComplete:function(){},onCancel:function(){},onUpload:function(){},onUploadChunk:function(){},onResume:function(){},onProgress:function(){},onError:function(){},onAutoRetry:function(){},onManualRetry:function(){},onValidateBatch:function(){},onValidate:function(){},onSubmitDelete:function(){},onDelete:function(){},onDeleteComplete:function(){},onPasteReceived:function(){},onStatusChange:function(){}},messages:{typeError:"{file} has an invalid extension. Valid extension(s): {extensions}.",sizeError:"{file} is too large, maximum file size is {sizeLimit}.",minSizeError:"{file} is too small, minimum file size is {minSizeLimit}.",emptyError:"{file} is empty, please select files again without it.",noFilesError:"No files to upload.",tooManyItemsError:"Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",retryFailTooManyItems:"Retry failed - you have reached your file limit.",onLeave:"The files are being uploaded, if you leave now the upload will be cancelled."},retry:{enableAuto:!1,maxAutoAttempts:3,autoAttemptDelay:5,preventRetryResponseProperty:"preventRetry"},classes:{buttonHover:"qq-upload-button-hover",buttonFocus:"qq-upload-button-focus"},chunking:{enabled:!1,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalFileSize:"qqtotalfilesize",totalParts:"qqtotalparts"}},resume:{enabled:!1,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},formatFileName:function(a){return void 0!==a&&a.length>33&&(a=a.slice(0,19)+"..."+a.slice(-14)),a},text:{defaultResponseError:"Upload failure reason unknown",sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:!1,method:"DELETE",endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:!1,sendCredentials:!1,allowXdr:!1},blobs:{defaultName:"misc_data"},paste:{targetElement:null,defaultName:"pasted_image"},camera:{ios:!1}},qq.extend(this._options,a,!0),this._handleCameraAccess(),this._wrapCallbacks(),this._disposeSupport=new qq.DisposeSupport,this._filesInProgress=[],this._storedIds=[],this._autoRetries=[],this._retryTimeouts=[],this._preventRetries=[],this._netUploadedOrQueued=0,this._netUploaded=0,this._uploadData=this._createUploadDataTracker(),this._paramsStore=this._createParamsStore("request"),this._deleteFileParamsStore=this._createParamsStore("deleteFile"),this._endpointStore=this._createEndpointStore("request"),this._deleteFileEndpointStore=this._createEndpointStore("deleteFile"),this._handler=this._createUploadHandler(),this._deleteHandler=this._createDeleteHandler(),this._options.button&&(this._button=this._createUploadButton(this._options.button)),this._options.paste.targetElement&&(this._pasteHandler=this._createPasteHandler()),this._preventLeaveInProgress()},qq.FineUploaderBasic.prototype={log:function(a,b){!this._options.debug||b&&"info"!==b?b&&"info"!==b&&qq.log("[FineUploader "+qq.version+"] "+a,b):qq.log("[FineUploader "+qq.version+"] "+a)},setParams:function(a,b){null==b?this._options.request.params=a:this._paramsStore.setParams(a,b)},setDeleteFileParams:function(a,b){null==b?this._options.deleteFile.params=a:this._deleteFileParamsStore.setParams(a,b)},setEndpoint:function(a,b){null==b?this._options.request.endpoint=a:this._endpointStore.setEndpoint(a,b)},getInProgress:function(){return this._filesInProgress.length},getNetUploads:function(){return this._netUploaded},uploadStoredFiles:function(){var a;if(0===this._storedIds.length)this._itemError("noFilesError");else for(;this._storedIds.length;)a=this._storedIds.shift(),this._filesInProgress.push(a),this._handler.upload(a)},clearStoredFiles:function(){this._storedIds=[]},retry:function(a){return this._onBeforeManualRetry(a)?(this._netUploadedOrQueued++,this._uploadData.setStatus(a,qq.status.UPLOAD_RETRYING),this._handler.retry(a),!0):!1},cancel:function(a){this._handler.cancel(a)},cancelAll:function(){var a=[],b=this;qq.extend(a,this._storedIds),qq.each(a,function(a,c){b.cancel(c)}),this._handler.cancelAll()},reset:function(){this.log("Resetting uploader..."),this._handler.reset(),this._filesInProgress=[],this._storedIds=[],this._autoRetries=[],this._retryTimeouts=[],this._preventRetries=[],this._button.reset(),this._paramsStore.reset(),this._endpointStore.reset(),this._netUploadedOrQueued=0,this._netUploaded=0,this._uploadData.reset(),this._pasteHandler&&this._pasteHandler.reset()},addFiles:function(a,b,c){var d,e,f,g=this,h=[];if(a){for(qq.isFileList(a)||(a=[].concat(a)),d=0;d=0&&this._storedIds.splice(b,1),this._uploadData.setStatus(a,qq.status.CANCELED)},_isDeletePossible:function(){return this._options.deleteFile.enabled?this._options.cors.expected?qq.supportedFeatures.deleteFileCorsXhr?!0:qq.supportedFeatures.deleteFileCorsXdr&&this._options.cors.allowXdr?!0:!1:!0:!1},_onSubmitDelete:function(a,b){return this._isDeletePossible()?this._handleCheckedCallback({name:"onSubmitDelete",callback:qq.bind(this._options.callbacks.onSubmitDelete,this,a),onSuccess:b||qq.bind(this._deleteHandler.sendDelete,this,a,this.getUuid(a)),identifier:a}):(this.log("Delete request ignored for ID "+a+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn"),!1)},_onDelete:function(a){this._uploadData.setStatus(a,qq.status.DELETING)},_onDeleteComplete:function(a,b,c){var d=this._handler.getName(a);c?(this._uploadData.setStatus(a,qq.status.DELETE_FAILED),this.log("Delete request for '"+d+"' has failed.","error"),void 0===b.withCredentials?this._options.callbacks.onError(a,d,"Delete request failed",b):this._options.callbacks.onError(a,d,"Delete request failed with response code "+b.status,b)):(this._netUploadedOrQueued--,this._netUploaded--,this._handler.expunge(a),this._uploadData.setStatus(a,qq.status.DELETED),this.log("Delete request for '"+d+"' has succeeded."))},_removeFromFilesInProgress:function(a){var b=qq.indexOf(this._filesInProgress,a);b>=0&&this._filesInProgress.splice(b,1)},_onUpload:function(a){this._uploadData.setStatus(a,qq.status.UPLOADING)},_onInputChange:function(a){qq.supportedFeatures.ajaxUploading?this.addFiles(a.files):this.addFiles(a),this._button.reset()},_onBeforeAutoRetry:function(a,b){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+b+"...")},_onAutoRetry:function(a,b){this.log("Retrying "+b+"..."),this._autoRetries[a]++,this._uploadData.setStatus(a,qq.status.UPLOAD_RETRYING),this._handler.retry(a)},_shouldAutoRetry:function(a){return!this._preventRetries[a]&&this._options.retry.enableAuto?(void 0===this._autoRetries[a]&&(this._autoRetries[a]=0),this._autoRetries[a]0&&this._netUploadedOrQueued+1>b?(this._itemError("retryFailTooManyItems"),!1):(this.log("Retrying upload for '"+c+"' (id: "+a+")..."),this._filesInProgress.push(a),!0)}return this.log("'"+a+"' is not a valid file ID","error"),!1},_maybeParseAndSendUploadError:function(a,b,c,d){if(!c.success)if(d&&200!==d.status&&!c.error)this._options.callbacks.onError(a,b,"XHR returned response code "+d.status,d);else{var e=c.error?c.error:this._options.text.defaultResponseError;this._options.callbacks.onError(a,b,e,d)}},_prepareItemsForUpload:function(a,b,c){var d=this._getValidationDescriptors(a);this._handleCheckedCallback({name:"onValidateBatch",callback:qq.bind(this._options.callbacks.onValidateBatch,this,d),onSuccess:qq.bind(this._onValidateBatchCallbackSuccess,this,d,a,b,c),identifier:"batch validation"})},_upload:function(a,b,c){var d=this._handler.add(a),e=this._handler.getName(d);this._uploadData.added(d),b&&this.setParams(b,d),c&&this.setEndpoint(c,d),this._handleCheckedCallback({name:"onSubmit",callback:qq.bind(this._options.callbacks.onSubmit,this,d,e),onSuccess:qq.bind(this._onSubmitCallbackSuccess,this,d,e),onFailure:qq.bind(this._fileOrBlobRejected,this,d,e),identifier:d})},_onSubmitCallbackSuccess:function(a){this._uploadData.setStatus(a,qq.status.SUBMITTED),this._onSubmit.apply(this,arguments),this._onSubmitted.apply(this,arguments),this._options.callbacks.onSubmitted.apply(this,arguments),this._options.autoUpload?this._handler.upload(a)||this._uploadData.setStatus(a,qq.status.QUEUED):this._storeForLater(a)},_onSubmitted:function(){},_storeForLater:function(a){this._storedIds.push(a)},_onValidateBatchCallbackSuccess:function(a,b,c,d){var e,f=this._options.validation.itemLimit,g=this._netUploadedOrQueued+a.length;0===f||f>=g?b.length>0?this._handleCheckedCallback({name:"onValidate",callback:qq.bind(this._options.callbacks.onValidate,this,b[0]),onSuccess:qq.bind(this._onValidateCallbackSuccess,this,b,0,c,d),onFailure:qq.bind(this._onValidateCallbackFailure,this,b,0,c,d),identifier:"Item '"+b[0].name+"', size: "+b[0].size}):this._itemError("noFilesError"):(e=this._options.messages.tooManyItemsError.replace(/\{netItems\}/g,g).replace(/\{itemLimit\}/g,f),this._batchError(e))},_onValidateCallbackSuccess:function(a,b,c,d){var e=b+1,f=this._getValidationDescriptor(a[b]),g=!1;this._validateFileOrBlobData(a[b],f)&&(g=!0,this._upload(a[b],c,d)),this._maybeProcessNextItemAfterOnValidateCallback(g,a,e,c,d)},_onValidateCallbackFailure:function(a,b,c,d){var e=b+1;this._fileOrBlobRejected(void 0,a[0].name),this._maybeProcessNextItemAfterOnValidateCallback(!1,a,e,c,d)},_maybeProcessNextItemAfterOnValidateCallback:function(a,b,c,d,e){var f=this;b.length>c&&(a||!this._options.validation.stopOnFirstInvalidFile)&&setTimeout(function(){var a=f._getValidationDescriptor(b[c]);f._handleCheckedCallback({name:"onValidate",callback:qq.bind(f._options.callbacks.onValidate,f,b[c]),onSuccess:qq.bind(f._onValidateCallbackSuccess,f,b,c,d,e),onFailure:qq.bind(f._onValidateCallbackFailure,f,b,c,d,e),identifier:"Item '"+a.name+"', size: "+a.size})},0)},_validateFileOrBlobData:function(a,b){var c=b.name,d=b.size,e=!0;return this._options.callbacks.onValidate(b)===!1&&(e=!1),qq.isFileOrInput(a)&&!this._isAllowedExtension(c)?(this._itemError("typeError",c),e=!1):0===d?(this._itemError("emptyError",c),e=!1):d&&this._options.validation.sizeLimit&&d>this._options.validation.sizeLimit?(this._itemError("sizeError",c),e=!1):d&&d999);return Math.max(a,.1).toFixed(1)+this._options.text.sizeSymbols[b]},_wrapCallbacks:function(){var a,b;a=this,b=function(b,c,d){try{return c.apply(a,d)}catch(e){a.log("Caught exception in '"+b+"' callback - "+e.message,"error")}};for(var c in this._options.callbacks)!function(){var d,e;d=c,e=a._options.callbacks[d],a._options.callbacks[d]=function(){return b(d,e,arguments)}}()},_parseFileOrBlobDataName:function(a){var b;return b=qq.isFileOrInput(a)?a.value?a.value.replace(/.*(\/|\\)/,""):null!==a.fileName&&void 0!==a.fileName?a.fileName:a.name:a.name},_parseFileOrBlobDataSize:function(a){var b;return qq.isFileOrInput(a)?a.value||(b=null!==a.fileSize&&void 0!==a.fileSize?a.fileSize:a.size):b=a.blob.size,b},_getValidationDescriptor:function(a){var b,c,d;return d={},b=this._parseFileOrBlobDataName(a),c=this._parseFileOrBlobDataSize(a),d.name=b,void 0!==c&&(d.size=c),d},_getValidationDescriptors:function(a){var b=this,c=[];return qq.each(a,function(a,d){c.push(b._getValidationDescriptor(d))
+}),c},_createParamsStore:function(a){var b={},c=this;return{setParams:function(a,c){var d={};qq.extend(d,a),b[c]=d},getParams:function(d){var e={};return null!=d&&b[d]?qq.extend(e,b[d]):qq.extend(e,c._options[a].params),e},remove:function(a){return delete b[a]},reset:function(){b={}}}},_createEndpointStore:function(a){var b={},c=this;return{setEndpoint:function(a,c){b[c]=a},getEndpoint:function(d){return null!=d&&b[d]?b[d]:c._options[a].endpoint},remove:function(a){return delete b[a]},reset:function(){b={}}}},_handleCameraAccess:function(){this._options.camera.ios&&qq.ios()&&(this._options.multiple=!1,null===this._options.validation.acceptFiles?this._options.validation.acceptFiles="image/*;capture=camera":this._options.validation.acceptFiles+=",image/*;capture=camera")}},qq.DragAndDrop=function(a){"use strict";function b(a){h.callbacks.dropLog("Grabbed "+a.length+" dropped files."),i.dropDisabled(!1),h.callbacks.processingDroppedFilesComplete(a)}function c(a){var b,d,e=new qq.Promise;return a.isFile?a.file(function(a){j.push(a),e.success()},function(b){h.callbacks.dropLog("Problem parsing '"+a.fullPath+"'. FileError code "+b.code+".","error"),e.failure()}):a.isDirectory&&(b=a.createReader(),b.readEntries(function(a){var b=a.length;for(d=0;d1&&!h.allowMultipleItems)h.callbacks.processingDroppedFilesComplete([]),h.callbacks.dropError("tooManyFilesError",""),i.dropDisabled(!1),g.failure();else{if(j=[],qq.isFolderDropSupported(a))for(d=a.items,b=0;b'+(this._options.dragAndDrop&&this._options.dragAndDrop.disableDefaultDropzone?"":'{dragZoneText}
')+(this._options.button?"":'')+'{dropProcessingText} '+(this._options.listElement?"":'')+"",fileTemplate:'
'+(this._options.editFilename&&this._options.editFilename.enabled?' ':"")+' '+(this._options.editFilename&&this._options.editFilename.enabled?' ':"")+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",editFilenameInput:"qq-edit-filename",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,editNameIcon:"qq-edit-filename-icon",editable:"qq-editable",dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:!0},messages:{tooManyFilesError:"You may only drop one file",unsupportedBrowser:"Unrecoverable error - this browser does not permit file uploading of any kind."},retry:{showAutoRetryNote:!0,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:!1},deleteFile:{forceConfirm:!1,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:!1,prependFiles:!1},paste:{promptForName:!1,namePromptMessage:"Please name this image"},editFilename:{enabled:!1},showMessage:function(a){setTimeout(function(){window.alert(a)},0)},showConfirm:function(a,b,c){setTimeout(function(){var d=window.confirm(a);d?b():c&&c()},0)},showPrompt:function(a,b){var c=new qq.Promise,d=window.prompt(a,b);return null!=d&&qq.trimStr(d).length>0?c.success(d):c.failure("Undefined or invalid user-supplied value."),c}},!0),qq.extend(this._options,a,!0),!qq.supportedFeatures.uploading||this._options.cors.expected&&!qq.supportedFeatures.uploadCors?this._options.element.innerHTML=""+this._options.messages.unsupportedBrowser+"
":(this._wrapCallbacks(),this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone),this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton),this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing),this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton),this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton),this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton),this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,""),this._element=this._options.element,this._element.innerHTML=this._options.template,this._listElement=this._options.listElement||this._find(this._element,"list"),this._classes=this._options.classes,this._button||(this._button=this._createUploadButton(this._find(this._element,"button"))),this._deleteRetryOrCancelClickHandler=this._bindDeleteRetryOrCancelClickEvent(),this._focusinEventSupported=!qq.firefox(),this._isEditFilenameEnabled()&&(this._filenameClickHandler=this._bindFilenameClickEvent(),this._filenameInputFocusInHandler=this._bindFilenameInputFocusInEvent(),this._filenameInputFocusHandler=this._bindFilenameInputFocusEvent()),this._dnd=this._setupDragAndDrop(),this._options.paste.targetElement&&this._options.paste.promptForName&&this._setupPastePrompt(),this._totalFilesInBatch=0,this._filesInBatchAddedToUi=0)},qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype),qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments),this._listElement.innerHTML=""},addExtraDropzone:function(a){this._dnd.setupExtraDropzone(a)},removeExtraDropzone:function(a){return this._dnd.removeDropzone(a)},getItemByFileId:function(a){for(var b=this._listElement.firstChild;b;){if(b.qqFileId==a)return b;b=b.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments),this._element.innerHTML=this._options.template,this._listElement=this._options.listElement||this._find(this._element,"list"),this._options.button||(this._button=this._createUploadButton(this._find(this._element,"button"))),this._dnd.dispose(),this._dnd=this._setupDragAndDrop(),this._totalFilesInBatch=0,this._filesInBatchAddedToUi=0},_removeFileItem:function(a){var b=this.getItemByFileId(a);qq(b).remove()},_setupDragAndDrop:function(){var a,b=this,c=this._find(this._element,"dropProcessing"),d=this._options.dragAndDrop.extraDropzones;return a=function(a){a.preventDefault()},this._options.dragAndDrop.disableDefaultDropzone||d.push(this._find(this._options.element,"drop")),new qq.DragAndDrop({dropZoneElements:d,hideDropZonesBeforeEnter:this._options.dragAndDrop.hideDropzones,allowMultipleItems:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{processingDroppedFiles:function(){var d=b._button.getInput();qq(c).css({display:"block"}),qq(d).attach("click",a)},processingDroppedFilesComplete:function(d){var e=b._button.getInput();qq(c).hide(),qq(e).detach("click",a),d&&b.addFiles(d)},dropError:function(a,c){b._itemError(a,c)},dropLog:function(a,c){b.log(a,c)}}})},_bindDeleteRetryOrCancelClickEvent:function(){var a=this;return new qq.DeleteRetryOrCancelClickHandler({listElement:this._listElement,classes:this._classes,log:function(b,c){a.log(b,c)},onDeleteFile:function(b){a.deleteFile(b)},onCancel:function(b){a.cancel(b)},onRetry:function(b){var c=a.getItemByFileId(b);qq(c).removeClass(a._classes.retryable),a.retry(b)},onGetName:function(b){return a.getName(b)}})},_isEditFilenameEnabled:function(){return this._options.editFilename.enabled&&!this._options.autoUpload},_filenameEditHandler:function(){var a=this;return{listElement:this._listElement,classes:this._classes,log:function(b,c){a.log(b,c)},onGetUploadStatus:function(b){return a.getUploads({id:b}).status},onGetName:function(b){return a.getName(b)},onSetName:function(b,c){var d=a.getItemByFileId(b),e=qq(a._find(d,"file")),f=a._options.formatFileName(c);e.setText(f),a.setName(b,c)},onGetInput:function(b){return a._find(b,"editFilenameInput")},onEditingStatusChange:function(b,c){var d=a.getItemByFileId(b),e=qq(a._find(d,"editFilenameInput")),f=qq(a._find(d,"file")),g=qq(a._find(d,"editNameIcon")),h=a._classes.editable;c?(e.addClass("qq-editing"),f.hide(),g.removeClass(h)):(e.removeClass("qq-editing"),f.css({display:""}),g.addClass(h)),qq(d).addClass("qq-temp").removeClass("qq-temp")}}},_onUploadStatusChange:function(a,b,c){if(this._isEditFilenameEnabled()){var d,e,f=this.getItemByFileId(a),g=this._classes.editable;f&&c!==qq.status.SUBMITTED&&(d=qq(this._find(f,"file")),e=qq(this._find(f,"editNameIcon")),d.removeClass(g),e.removeClass(g))}},_bindFilenameInputFocusInEvent:function(){var a=qq.extend({},this._filenameEditHandler());return new qq.FilenameInputFocusInHandler(a)},_bindFilenameInputFocusEvent:function(){var a=qq.extend({},this._filenameEditHandler());return new qq.FilenameInputFocusHandler(a)},_bindFilenameClickEvent:function(){var a=qq.extend({},this._filenameEditHandler());return new qq.FilenameClickHandler(a)},_leaving_document_out:function(a){return(qq.chrome()||qq.safari()&&qq.windows())&&0==a.clientX&&0==a.clientY||qq.firefox()&&!a.relatedTarget},_storeForLater:function(a){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var b=this.getItemByFileId(a);qq(this._find(b,"spinner")).hide()},_find:function(a,b){var c=qq(a).getByClass(this._options.classes[b])[0];if(!c)throw new Error("element not found "+b);return c},_onSubmit:function(a,b){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments),this._addToList(a,b)},_onSubmitted:function(a){if(this._isEditFilenameEnabled()){var b=this.getItemByFileId(a),c=qq(this._find(b,"file")),d=qq(this._find(b,"editNameIcon")),e=this._classes.editable;c.addClass(e),d.addClass(e),this._focusinEventSupported||this._filenameInputFocusHandler.addHandler(this._find(b,"editFilenameInput"))}},_onProgress:function(a,b,c,d){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var e,f,g,h;e=this.getItemByFileId(a),f=this._find(e,"progressBar"),g=Math.round(100*(c/d)),c===d?(h=this._find(e,"cancel"),qq(h).hide(),qq(f).hide(),qq(this._find(e,"statusText")).setText(this._options.text.waitingForResponse),this._displayFileSize(a)):(this._displayFileSize(a,c,d),qq(f).css({display:"block"})),qq(f).css({width:g+"%"})},_onComplete:function(a,b,c){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var d=this.getItemByFileId(a);qq(this._find(d,"statusText")).clearText(),qq(d).removeClass(this._classes.retrying),qq(this._find(d,"progressBar")).hide(),(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading)&&qq(this._find(d,"cancel")).hide(),qq(this._find(d,"spinner")).hide(),c.success?(this._isDeletePossible()&&this._showDeleteLink(a),qq(d).addClass(this._classes.success),this._classes.successIcon&&(this._find(d,"finished").style.display="inline-block",qq(d).addClass(this._classes.successIcon))):(qq(d).addClass(this._classes.fail),this._classes.failIcon&&(this._find(d,"finished").style.display="inline-block",qq(d).addClass(this._classes.failIcon)),this._options.retry.showButton&&!this._preventRetries[a]&&qq(d).addClass(this._classes.retryable),this._controlFailureTextDisplay(d,c))},_onUpload:function(a){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments),this._showSpinner(a)},_onCancel:function(a){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments),this._removeFileItem(a)},_onBeforeAutoRetry:function(a){var b,c,d,e,f,g;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments),b=this.getItemByFileId(a),c=this._find(b,"progressBar"),this._showCancelLink(b),c.style.width=0,qq(c).hide(),this._options.retry.showAutoRetryNote&&(d=this._find(b,"statusText"),e=this._autoRetries[a]+1,f=this._options.retry.maxAutoAttempts,g=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,e),g=g.replace(/\{maxAuto\}/g,f),qq(d).setText(g),1===e&&qq(b).addClass(this._classes.retrying))},_onBeforeManualRetry:function(a){var b=this.getItemByFileId(a);return qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)?(this._find(b,"progressBar").style.width=0,qq(b).removeClass(this._classes.fail),qq(this._find(b,"statusText")).clearText(),this._showSpinner(a),this._showCancelLink(b),!0):(qq(b).addClass(this._classes.retryable),!1)},_onSubmitDelete:function(a){var b=qq.bind(this._onSubmitDeleteSuccess,this,a);qq.FineUploaderBasic.prototype._onSubmitDelete.call(this,a,b)},_onSubmitDeleteSuccess:function(a){this._options.deleteFile.forceConfirm?this._showDeleteConfirm(a):this._sendDeleteRequest(a)},_onDeleteComplete:function(a,b,c){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var d=this.getItemByFileId(a),e=this._find(d,"spinner"),f=this._find(d,"statusText");qq(e).hide(),c?(qq(f).setText(this._options.deleteFile.deletingFailedText),this._showDeleteLink(a)):this._removeFileItem(a)},_sendDeleteRequest:function(a){var b=this.getItemByFileId(a),c=this._find(b,"deleteButton"),d=this._find(b,"statusText");qq(c).hide(),this._showSpinner(a),qq(d).setText(this._options.deleteFile.deletingStatusText),this._deleteHandler.sendDelete(a,this.getUuid(a))},_showDeleteConfirm:function(a){var b=this._handler.getName(a),c=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,b),d=(this.getUuid(a),this);this._options.showConfirm(c,function(){d._sendDeleteRequest(a)})},_addToList:function(a,b){var c=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.supportedFeatures.ajaxUploading){var d=this._find(c,"cancel");qq(d).remove()}c.qqFileId=a;var e=this._find(c,"file");qq(e).setText(this._options.formatFileName(b)),qq(this._find(c,"size")).hide(),this._options.multiple||(this._handler.cancelAll(),this._clearList()),this._options.display.prependFiles?this._prependItem(c):this._listElement.appendChild(c),this._filesInBatchAddedToUi+=1,this._options.display.fileSizeOnSubmit&&qq.supportedFeatures.ajaxUploading&&this._displayFileSize(a)},_prependItem:function(a){var b=this._listElement,c=b.firstChild;this._totalFilesInBatch>1&&this._filesInBatchAddedToUi>0&&(c=qq(b).children()[this._filesInBatchAddedToUi-1].nextSibling),b.insertBefore(a,c)},_clearList:function(){this._listElement.innerHTML="",this.clearStoredFiles()},_displayFileSize:function(a,b,c){var d=this.getItemByFileId(a),e=this.getSize(a),f=this._formatSize(e),g=this._find(d,"size");void 0!==b&&void 0!==c&&(f=this._formatProgress(b,c)),qq(g).css({display:"inline"}),qq(g).setText(f)},_formatProgress:function(a,b){function c(a,b){d=d.replace(a,b)}var d=this._options.text.formatProgress;return c("{percent}",Math.round(100*(a/b))),c("{total_size}",this._formatSize(b)),d},_controlFailureTextDisplay:function(a,b){var c,d,e,f,g;c=this._options.failedUploadTextDisplay.mode,d=this._options.failedUploadTextDisplay.maxChars,e=this._options.failedUploadTextDisplay.responseProperty,"custom"===c?(f=b[e],f?f.length>d&&(g=f.substring(0,d)+"..."):(f=this._options.text.failUpload,this.log("'"+e+"' is not a valid property on the server response.","warn")),qq(this._find(a,"statusText")).setText(g||f),this._options.failedUploadTextDisplay.enableTooltip&&this._showTooltip(a,f)):"default"===c?qq(this._find(a,"statusText")).setText(this._options.text.failUpload):"none"!==c&&this.log("failedUploadTextDisplay.mode value of '"+c+"' is not valid","warn")},_showTooltip:function(a,b){a.title=b},_showSpinner:function(a){var b=this.getItemByFileId(a),c=this._find(b,"spinner");c.style.display="inline-block"},_showCancelLink:function(a){if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){var b=this._find(a,"cancel");qq(b).css({display:"inline"})}},_showDeleteLink:function(a){var b=this.getItemByFileId(a),c=this._find(b,"deleteButton");qq(c).css({display:"inline"})},_itemError:function(){var a=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(a)},_batchError:function(a){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments),this._options.showMessage(a)},_setupPastePrompt:function(){var a=this;this._options.callbacks.onPasteReceived=function(){var b=a._options.paste.namePromptMessage,c=a._options.paste.defaultName;return a._options.showPrompt(b,c)}},_fileOrBlobRejected:function(){this._totalFilesInBatch-=1,qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this,arguments)},_prepareItemsForUpload:function(a){this._totalFilesInBatch=a.length,this._filesInBatchAddedToUi=0,qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this,arguments)}}),qq.AjaxRequestor=function(a){"use strict";function b(){return qq.indexOf(["GET","POST","HEAD"],v.method)>=0}function c(){var a=!1;return qq.each(a,function(b,c){return qq.indexOf(["Accept","Accept-Language","Content-Language","Content-Type"],c)<0?(a=!0,!1):void 0}),a}function d(a){return v.cors.expected&&void 0===a.withCredentials}function e(){var a;return window.XMLHttpRequest&&(a=new XMLHttpRequest,void 0===a.withCredentials&&(a=new XDomainRequest)),a}function f(a,b){var c=u[a].xhr;return c||b||(c=v.cors.expected?e():new XMLHttpRequest,u[a].xhr=c),c}function g(a){var b,c=qq.indexOf(t,a),d=v.maxConnections;delete u[a],t.splice(c,1),t.length>=d&&d>c&&(b=t[d-1],j(b))}function h(a,b){var c=f(a),e=v.method,h=b===!1;g(a),h?r(e+" request for "+a+" has failed","error"):d(c)||q(c.status)||(h=!0,r(e+" request for "+a+" has failed - response code "+c.status,"error")),v.onComplete(a,c,h)}function i(a){var b={},c=u[a].additionalParams,d=v.mandatedParams;return v.paramsStore.getParams&&(b=v.paramsStore.getParams(a)),c&&qq.each(c,function(a,c){b[a]=c}),d&&qq.each(d,function(a,c){b[a]=c}),b}function j(a){var b,c=f(a),e=v.method,g=i(a);v.onSend(a),b=k(a,g),d(c)?(c.onload=m(a),c.onerror=n(a)):c.onreadystatechange=l(a),c.open(e,b,!0),v.cors.expected&&v.cors.sendCredentials&&!d(c)&&(c.withCredentials=!0),o(a),r("Sending "+e+" request for "+a),!s&&g?c.send(qq.obj2url(g,"")):c.send()}function k(a,b){var c=v.endpointStore.getEndpoint(a),d=u[a].addToPath;return void 0!=d&&(c+="/"+d),s&&b?qq.obj2url(b,c):c}function l(a){return function(){4===f(a).readyState&&h(a)}}function m(a){return function(){h(a)}}function n(a){return function(){h(a,!0)}}function o(a){var e=f(a),g=v.customHeaders;d(e)&&(v.cors.expected&&b()&&!c(g)||(e.setRequestHeader("X-Requested-With","XMLHttpRequest"),e.setRequestHeader("Cache-Control","no-cache"))),"POST"!==v.method&&"PUT"!==v.method||d(e)||e.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),d(e)||qq.each(g,function(a,b){e.setRequestHeader(a,b)})}function p(a){var b=f(a,!0),c=v.method;return b?(d(b)?(b.onerror=null,b.onload=null):b.onreadystatechange=null,b.abort(),g(a),r("Cancelled "+c+" for "+a),v.onCancel(a),!0):!1}function q(a){return qq.indexOf(v.successfulResponseCodes[v.method],a)>=0}var r,s,t=[],u=[],v={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},mandatedParams:{},successfulResponseCodes:{DELETE:[200,202,204],POST:[200,204]},cors:{expected:!1,sendCredentials:!1},log:function(){},onSend:function(){},onComplete:function(){},onCancel:function(){}};return qq.extend(v,a),r=v.log,s="GET"===v.method||"DELETE"===v.method,{send:function(a,b,c){u[a]={addToPath:b,additionalParams:c};var d=t.push(a);d<=v.maxConnections&&j(a)},cancel:function(a){return p(a)}}},qq.DeleteFileAjaxRequestor=function(a){"use strict";function b(){return f.method.toUpperCase()}function c(){return"POST"===b()?{_method:"DELETE"}:{}}var d,e=["POST","DELETE"],f={method:"DELETE",uuidParamName:"qquuid",endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:!1,cors:{expected:!1,sendCredentials:!1},log:function(){},onDelete:function(){},onDeleteComplete:function(){}};if(qq.extend(f,a),qq.indexOf(e,b())<0)throw new Error("'"+b()+"' is not a supported method for delete file requests!");return d=new qq.AjaxRequestor({method:b(),endpointStore:f.endpointStore,paramsStore:f.paramsStore,mandatedParams:c(),maxConnections:f.maxConnections,customHeaders:f.customHeaders,demoMode:f.demoMode,log:f.log,onSend:f.onDelete,onComplete:f.onDeleteComplete,cors:f.cors}),{sendDelete:function(a,c){var e={};f.log("Submitting delete file request for "+a),"DELETE"===b()?d.send(a,c):(e[f.uuidParamName]=c,d.send(a,null,e))}}},qq.WindowReceiveMessage=function(a){var b={log:function(){}},c={};return qq.extend(b,a),{receiveMessage:function(a,b){var d=function(a){b(a.data)};window.postMessage?c[a]=qq(window).attach("message",d):log("iframe message passing not supported in this browser!","error")},stopReceivingMessages:function(a){if(window.postMessage){var b=c[a];b&&b()}}}},qq.UploadHandler=function(a){"use strict";function b(a){var b,c=qq.indexOf(h,a),e=d.maxConnections;c>=0&&(h.splice(c,1),h.length>=e&&e>c&&(b=h[e-1],f.upload(b)))}function c(a){e("Cancelling "+a),d.paramsStore.remove(a),b(a)}var d,e,f,g,h=[];return d={debug:!1,forceMultipart:!0,paramsInBody:!1,paramsStore:{},endpointStore:{},filenameParam:"qqfilename",cors:{expected:!1,sendCredentials:!1},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:!1,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:!1,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},log:function(){},onProgress:function(){},onComplete:function(){},onCancel:function(){},onUpload:function(){},onUploadChunk:function(){},onAutoRetry:function(){},onResume:function(){},onUuidChanged:function(){}},qq.extend(d,a),e=d.log,f=qq.supportedFeatures.ajaxUploading?new qq.UploadHandlerXhr(d,b,d.onUuidChanged,e):new qq.UploadHandlerForm(d,b,d.onUuidChanged,e),g={add:function(a){return f.add(a)},upload:function(a){var b=h.push(a);return b<=d.maxConnections?(f.upload(a),!0):!1},retry:function(a){var b=qq.indexOf(h,a);return b>=0?f.upload(a,!0):this.upload(a)},cancel:function(a){var b=f.cancel(a);qq.isPromise(b)?b.then(function(){c(a)}):b!==!1&&c(a)},cancelAll:function(){var a=this,b=[];qq.extend(b,h),qq.each(b,function(b,c){a.cancel(c)}),h=[]},getName:function(a){return f.getName(a)},setName:function(a,b){f.setName(a,b)},getSize:function(a){return f.getSize?f.getSize(a):void 0},getFile:function(a){return f.getFile?f.getFile(a):void 0},reset:function(){e("Resetting upload handler"),g.cancelAll(),h=[],f.reset()},expunge:function(a){return f.expunge(a)},getUuid:function(a){return f.getUuid(a)},isValid:function(a){return f.isValid(a)},getResumableFilesData:function(){return f.getResumableFilesData?f.getResumableFilesData():[]}}},qq.UploadHandlerForm=function(a,b,c,d){"use strict";function e(a){void 0!==t[a]&&(t[a](),delete t[a])}function f(a,b){var c=a.id,d=m(c);y[r[d]]=b,t[d]=qq(a).attach("load",function(){q[d]&&(w("Received iframe load event for CORS upload request (iframe name "+c+")"),u[c]=setTimeout(function(){var a="No valid message received from loaded iframe for iframe name "+c;w(a,"error"),b({error:a})},1e3))}),x.receiveMessage(c,function(a){w("Received the following window message: '"+a+"'");var b,d=i(m(c),a),f=d.uuid;f&&y[f]?(w("Handling response for iframe name "+c),clearTimeout(u[c]),delete u[c],e(c),b=y[f],delete y[f],x.stopReceivingMessages(c),b(d)):f||w("'"+a+"' does not contain a UUID - ignoring.")})}function g(a,b){p.cors.expected?f(a,b):t[a.id]=qq(a).attach("load",function(){if(w("Received response for "+a.id),a.parentNode){try{if(a.contentDocument&&a.contentDocument.body&&"false"==a.contentDocument.body.innerHTML)return}catch(c){w("Error when attempting to access iframe during handling of upload response ("+c+")","error")}b()}})}function h(a,b){var c;try{var d=b.contentDocument||b.contentWindow.document,e=d.body.innerHTML;w("converting iframe's innerHTML to JSON"),w("innerHTML = "+e),e&&e.match(/^ ');return c.setAttribute("id",b),c.style.display="none",document.body.appendChild(c),c}function k(a,b){var c=p.paramsStore.getParams(a),d=p.demoMode?"GET":"POST",e=qq.toElement(''),f=p.endpointStore.getEndpoint(a),g=f;return c[p.uuidParamName]=r[a],void 0!==s[a]&&(c[p.filenameParam]=s[a]),p.paramsInBody?qq.obj2Inputs(c,e):g=qq.obj2url(c,f),e.setAttribute("action",g),e.setAttribute("target",b.name),e.style.display="none",document.body.appendChild(e),e}function l(a){delete q[a],delete r[a],delete t[a],p.cors.expected&&(clearTimeout(u[a]),delete u[a],x.stopReceivingMessages(a));var b=document.getElementById(n(a));b&&(b.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;"),qq(b).remove())}function m(a){return a.split("_")[0]}function n(a){return a+"_"+z}var o,p=a,q=[],r=[],s=[],t={},u={},v=b,w=d,x=new qq.WindowReceiveMessage({log:w}),y={},z=qq.getUniqueId();return o={add:function(a){a.setAttribute("name",p.inputName);var b=q.push(a)-1;return r[b]=qq.getUniqueId(),a.parentNode&&qq(a).remove(),b},getName:function(a){return void 0!==s[a]?s[a]:o.isValid(a)?q[a].value.replace(/.*(\/|\\)/,""):(w(a+" is not a valid item ID.","error"),void 0)},setName:function(a,b){s[a]=b},isValid:function(a){return void 0!==q[a]},reset:function(){q=[],r=[],s=[],t={},z=qq.getUniqueId()},expunge:function(a){return l(a)},getUuid:function(a){return r[a]},cancel:function(a){var b=p.onCancel(a,o.getName(a));return qq.isPromise(b)?b.then(function(){l(a)}):b!==!1?(l(a),!0):!1},upload:function(a){var b,c=q[a],d=o.getName(a),f=j(a);if(!c)throw new Error("file with passed id was not added, or already uploaded or cancelled");p.onUpload(a,o.getName(a)),b=k(a,f),b.appendChild(c),g(f,function(b){w("iframe loaded");var c=b?b:h(a,f);e(a),p.cors.expected||qq(f).remove(),(c.success||!p.onAutoRetry(a,d,c))&&(p.onComplete(a,d,c),v(a))}),w("Sending upload request for "+a),b.submit(),qq(b).remove()}}},qq.UploadHandlerXhr=function(a,b,c,d){"use strict";function e(a,b,c){var d=K.getSize(a),e=K.getName(a);b[L.chunking.paramNames.partIndex]=c.part,b[L.chunking.paramNames.partByteOffset]=c.start,b[L.chunking.paramNames.chunkSize]=c.size,b[L.chunking.paramNames.totalParts]=c.count,b[L.totalFileSizeParamName]=d,T&&(b[L.filenameParam]=e)}function f(a){a[L.resume.paramNames.resuming]=!0}function g(a,b,c){return a.slice?a.slice(b,c):a.mozSlice?a.mozSlice(b,c):a.webkitSlice?a.webkitSlice(b,c):void 0}function h(a,b){var c=L.chunking.partSize,d=K.getSize(a),e=O[a].file||O[a].blobData.blob,f=c*b,h=f+c>=d?d:f+c,j=i(a);return{part:b,start:f,end:h,count:j,blob:g(e,f,h),size:h-f}}function i(a){var b=K.getSize(a),c=L.chunking.partSize;return Math.ceil(b/c)}function j(a){var b=new XMLHttpRequest;
+return O[a].xhr=b,b}function k(a,b,c,d){var e=new FormData,f=L.demoMode?"GET":"POST",g=L.endpointStore.getEndpoint(d),h=g,i=K.getName(d),j=K.getSize(d),k=O[d].blobData,l=O[d].newName;return a[L.uuidParamName]=O[d].uuid,T&&(a[L.totalFileSizeParamName]=j,k&&(a[L.filenameParam]=k.name)),void 0!==l&&(a[L.filenameParam]=l),L.paramsInBody||(T||(a[L.inputName]=l||i),h=qq.obj2url(a,g)),b.open(f,h,!0),L.cors.expected&&L.cors.sendCredentials&&(b.withCredentials=!0),T?(L.paramsInBody&&qq.obj2FormData(a,e),e.append(L.inputName,c),e):c}function l(a,b){var c=L.customHeaders,d=O[a].file||O[a].blobData.blob;b.setRequestHeader("X-Requested-With","XMLHttpRequest"),b.setRequestHeader("Cache-Control","no-cache"),T||(b.setRequestHeader("Content-Type","application/octet-stream"),b.setRequestHeader("X-Mime-Type",d.type)),qq.each(c,function(a,c){b.setRequestHeader(a,c)})}function m(a,b,c){var d=K.getName(a),e=K.getSize(a);O[a].attemptingResume=!1,L.onProgress(a,d,e,e),L.onComplete(a,d,b,c),O[a]&&delete O[a].xhr,M(a)}function n(a){var b,c,d=O[a].remainingChunkIdxs[0],g=h(a,d),i=j(a),m=K.getSize(a),n=K.getName(a);void 0===O[a].loaded&&(O[a].loaded=0),R&&O[a].file&&z(a,g),i.onreadystatechange=y(a,i),i.upload.onprogress=function(b){if(b.lengthComputable){var c=b.loaded+O[a].loaded,e=o(a,d,b.total);L.onProgress(a,n,c,e)}},L.onUploadChunk(a,n,x(g)),c=L.paramsStore.getParams(a),e(a,c,g),O[a].attemptingResume&&f(c),b=k(c,i,g.blob,a),l(a,i),N("Sending chunked upload request for item "+a+": bytes "+(g.start+1)+"-"+g.end+" of "+m),i.send(b)}function o(a,b,c){var d=h(a,b),e=d.size,f=c-e,g=K.getSize(a),i=d.count,j=O[a].initialRequestOverhead,k=f-j;return O[a].lastRequestOverhead=f,0===b?(O[a].lastChunkIdxProgress=0,O[a].initialRequestOverhead=f,O[a].estTotalRequestsSize=g+i*f):O[a].lastChunkIdxProgress!==b&&(O[a].lastChunkIdxProgress=b,O[a].estTotalRequestsSize+=k),O[a].estTotalRequestsSize}function p(a){return T?O[a].lastRequestOverhead:0}function q(a,b,c){var d=O[a].remainingChunkIdxs.shift(),e=h(a,d);O[a].attemptingResume=!1,O[a].loaded+=e.size+p(a),O[a].remainingChunkIdxs.length>0?n(a):(R&&A(a),m(a,b,c))}function r(a,b){return 200!==a.status||!b.success||b.reset}function s(a,b){var d;try{d=qq.parseJson(b.responseText),void 0!==d.newUuid&&(N("Server requested UUID change from '"+O[a].uuid+"' to '"+d.newUuid+"'"),O[a].uuid=d.newUuid,c(a,d.newUuid))}catch(e){N("Error when attempting to parse xhr response text ("+e+")","error"),d={}}return d}function t(a){N("Server has ordered chunking effort to be restarted on next attempt for item ID "+a,"error"),R&&(A(a),O[a].attemptingResume=!1),O[a].remainingChunkIdxs=[],delete O[a].loaded,delete O[a].estTotalRequestsSize,delete O[a].initialRequestOverhead}function u(a){O[a].attemptingResume=!1,N("Server has declared that it cannot handle resume for item ID "+a+" - starting from the first chunk","error"),t(a),K.upload(a,!0)}function v(a,b,c){var d=K.getName(a);L.onAutoRetry(a,d,b,c)||m(a,b,c)}function w(a,b){var c;O[a]&&(N("xhr - server response received for "+a),N("responseText = "+b.responseText),c=s(a,b),r(b,c)?(c.reset&&t(a),O[a].attemptingResume&&c.reset?u(a):v(a,c,b)):Q?q(a,c,b):m(a,c,b))}function x(a){return{partIndex:a.part,startByte:a.start+1,endByte:a.end,totalParts:a.count}}function y(a,b){return function(){4===b.readyState&&w(a,b)}}function z(a,b){var c=K.getUuid(a),d=O[a].loaded,e=O[a].initialRequestOverhead,f=O[a].estTotalRequestsSize,g=C(a),h=c+P+b.part+P+d+P+e+P+f,i=L.resume.cookiesExpireIn;qq.setCookie(g,h,i)}function A(a){if(O[a].file){var b=C(a);qq.deleteCookie(b)}}function B(a){var b,c,d,e,f,g,h=qq.getCookie(C(a)),i=K.getName(a);if(h){if(b=h.split(P),5===b.length)return c=b[0],d=parseInt(b[1],10),e=parseInt(b[2],10),f=parseInt(b[3],10),g=parseInt(b[4],10),{uuid:c,part:d,lastByteSent:e,initialRequestOverhead:f,estTotalRequestsSize:g};N("Ignoring previously stored resume/chunk cookie for "+i+" - old cookie format","warn")}}function C(a){var b,c=K.getName(a),d=K.getSize(a),e=L.chunking.partSize;return b="qqfilechunk"+P+encodeURIComponent(c)+P+d+P+e,void 0!==S&&(b+=P+S),b}function D(){return null===L.resume.id||void 0===L.resume.id||qq.isFunction(L.resume.id)||qq.isObject(L.resume.id)?void 0:L.resume.id}function E(a,b){var c;for(c=i(a)-1;c>=b;c-=1)O[a].remainingChunkIdxs.unshift(c);n(a)}function F(a,b,c,d){c=d.part,O[a].loaded=d.lastByteSent,O[a].estTotalRequestsSize=d.estTotalRequestsSize,O[a].initialRequestOverhead=d.initialRequestOverhead,O[a].attemptingResume=!0,N("Resuming "+b+" at partition index "+c),E(a,c)}function G(a,b,c){var d,e=K.getName(a),f=h(a,b.part);d=L.onResume(a,e,x(f)),qq.isPromise(d)?(N("Waiting for onResume promise to be fulfilled for "+a),d.then(function(){F(a,e,c,b)},function(){N("onResume promise fulfilled - failure indicated. Will not resume."),E(a,c)})):d!==!1?F(a,e,c,b):(N("onResume callback returned false. Will not resume."),E(a,c))}function H(a,b){var c,d=0;O[a].remainingChunkIdxs&&0!==O[a].remainingChunkIdxs.length?n(a):(O[a].remainingChunkIdxs=[],R&&!b&&O[a].file?(c=B(a),c?G(a,c,d):E(a,d)):E(a,d))}function I(a){var b,c,d,e=O[a].file||O[a].blobData.blob,f=K.getName(a);O[a].loaded=0,b=j(a),b.upload.onprogress=function(b){b.lengthComputable&&(O[a].loaded=b.loaded,L.onProgress(a,f,b.loaded,b.total))},b.onreadystatechange=y(a,b),c=L.paramsStore.getParams(a),d=k(c,b,e,a),l(a,b),N("Sending upload request for "+a),b.send(d)}function J(a){var b=O[a].xhr;b&&(b.onreadystatechange=null,b.abort()),R&&A(a),delete O[a]}var K,L=a,M=b,N=d,O=[],P="|",Q=L.chunking.enabled&&qq.supportedFeatures.chunking,R=L.resume.enabled&&Q&&qq.supportedFeatures.resume,S=D(),T=L.forceMultipart||L.paramsInBody;return K={add:function(a){var b,c,d=qq.getUniqueId();if(qq.isFile(a))b=O.push({file:a})-1;else{if(!qq.isBlob(a.blob))throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)");b=O.push({blobData:a})-1}return R&&(c=B(b),c&&(d=c.uuid)),O[b].uuid=d,b},getName:function(a){if(K.isValid(a)){var b=O[a].file,c=O[a].blobData,d=O[a].newName;return void 0!==d?d:b?null!==b.fileName&&void 0!==b.fileName?b.fileName:b.name:c.name}N(a+" is not a valid item ID.","error")},setName:function(a,b){O[a].newName=b},getSize:function(a){var b=O[a].file||O[a].blobData.blob;return qq.isFileOrInput(b)?null!=b.fileSize?b.fileSize:b.size:b.size},getFile:function(a){return O[a]?O[a].file||O[a].blobData.blob:void 0},isValid:function(a){return void 0!==O[a]},reset:function(){O=[]},expunge:function(a){return J(a)},getUuid:function(a){return O[a].uuid},upload:function(a,b){var c=this.getName(a);this.isValid(a)&&(L.onUpload(a,c),Q?H(a,b):I(a))},cancel:function(a){var b=L.onCancel(a,this.getName(a));return qq.isPromise(b)?b.then(function(){J(a)}):b!==!1?(J(a),!0):!1},getResumableFilesData:function(){var a=[],b=[];return Q&&R?(a=void 0===S?qq.getCookieNames(new RegExp("^qqfilechunk\\"+P+".+\\"+P+"\\d+\\"+P+L.chunking.partSize+"=")):qq.getCookieNames(new RegExp("^qqfilechunk\\"+P+".+\\"+P+"\\d+\\"+P+L.chunking.partSize+"\\"+P+S+"=")),qq.each(a,function(a,c){var d=c.split(P),e=qq.getCookie(c).split(P);b.push({name:decodeURIComponent(d[1]),size:d[2],uuid:e[0],partIdx:e[1]})}),b):[]}}},qq.UiEventHandler=function(a,b){"use strict";function c(a){d.attach(a,e.eventType,function(a){a=a||window.event;var b=a.target||a.srcElement;e.onHandled(b,a)})}var d=new qq.DisposeSupport,e={eventType:"click",attachTo:null,onHandled:function(){}},f={addHandler:function(a){c(a)},dispose:function(){d.dispose()}};return qq.extend(b,{getItemFromEventTarget:function(a){for(var b=a.parentNode;void 0===b.qqFileId;)b=b.parentNode;return b},getFileIdFromItem:function(a){return a.qqFileId},getDisposeSupport:function(){return d}}),qq.extend(e,a),e.attachTo&&c(e.attachTo),f},qq.DeleteRetryOrCancelClickHandler=function(a){"use strict";function b(a,b){if(qq(a).hasClass(e.classes.cancel)||qq(a).hasClass(e.classes.retry)||qq(a).hasClass(e.classes.deleteButton)){var f=d.getItemFromEventTarget(a),g=d.getFileIdFromItem(f);qq.preventDefault(b),e.log(qq.format("Detected valid cancel, retry, or delete click event on file '{}', ID: {}.",e.onGetName(g),g)),c(a,g)}}function c(a,b){qq(a).hasClass(e.classes.deleteButton)?e.onDeleteFile(b):qq(a).hasClass(e.classes.cancel)?e.onCancel(b):e.onRetry(b)}var d={},e={listElement:document,log:function(){},classes:{cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry"},onDeleteFile:function(){},onCancel:function(){},onRetry:function(){},onGetName:function(){}};qq.extend(e,a),e.eventType="click",e.onHandled=b,e.attachTo=e.listElement,qq.extend(this,new qq.UiEventHandler(e,d))},qq.FilenameEditHandler=function(a,b){"use strict";function c(a){var b=i.onGetName(a),c=b.lastIndexOf(".");return c>0&&(b=b.substr(0,c)),b}function d(a){var b=i.onGetName(a),c=b.lastIndexOf(".");return c>0?b.substr(c,b.length-c):void 0}function e(a,b){var c,e=a.value;void 0!==e&&qq.trimStr(e).length>0&&(c=d(b),void 0!==c&&(e+=d(b)),i.onSetName(b,e)),i.onEditingStatusChange(b,!1)}function f(a,c){b.getDisposeSupport().attach(a,"blur",function(){e(a,c)})}function g(a,c){b.getDisposeSupport().attach(a,"keyup",function(b){var d=b.keyCode||b.which;13===d&&e(a,c)})}var h,i={listElement:null,log:function(){},classes:{file:"qq-upload-file"},onGetUploadStatus:function(){},onGetName:function(){},onSetName:function(){},onGetInput:function(){},onEditingStatusChange:function(){}};return qq.extend(i,a),i.attachTo=i.listElement,h=qq.extend(this,new qq.UiEventHandler(i,b)),qq.extend(b,{handleFilenameEdit:function(a,b,d,e){var h=i.onGetInput(d);i.onEditingStatusChange(a,!0),h.value=c(a),e&&h.focus(),f(h,a),g(h,a)}}),h},qq.FilenameClickHandler=function(a){"use strict";function b(a,b){if(qq(a).hasClass(d.classes.file)||qq(a).hasClass(d.classes.editNameIcon)){var e=c.getItemFromEventTarget(a),f=c.getFileIdFromItem(e),g=d.onGetUploadStatus(f);g===qq.status.SUBMITTED&&(d.log(qq.format("Detected valid filename click event on file '{}', ID: {}.",d.onGetName(f),f)),qq.preventDefault(b),c.handleFilenameEdit(f,a,e,!0))}}var c={},d={log:function(){},classes:{file:"qq-upload-file",editNameIcon:"qq-edit-filename-icon"},onGetUploadStatus:function(){},onGetName:function(){}};return qq.extend(d,a),d.eventType="click",d.onHandled=b,qq.extend(this,new qq.FilenameEditHandler(d,c))},qq.FilenameInputFocusInHandler=function(a,b){"use strict";function c(a){if(qq(a).hasClass(d.classes.editFilenameInput)){var c=b.getItemFromEventTarget(a),e=b.getFileIdFromItem(c),f=d.onGetUploadStatus(e);f===qq.status.SUBMITTED&&(d.log(qq.format("Detected valid filename input focus event on file '{}', ID: {}.",d.onGetName(e),e)),b.handleFilenameEdit(e,a,c))}}var d={listElement:null,classes:{editFilenameInput:"qq-edit-filename"},onGetUploadStatus:function(){},log:function(){}};return b||(b={}),d.eventType="focusin",d.onHandled=c,qq.extend(d,a),qq.extend(this,new qq.FilenameEditHandler(d,b))},qq.FilenameInputFocusHandler=function(a){"use strict";return a.eventType="focus",a.attachTo=null,qq.extend(this,new qq.FilenameInputFocusInHandler(a,{}))},function(a){"use strict";var b,c,d,e,f,g,h,i,j,k;g=["uploaderType"],d=function(a){if(a){var d=i(a);h(d),"basic"===f("uploaderType")?b(new qq.FineUploaderBasic(d)):b(new qq.FineUploader(d))}return c},e=function(a,b){var d=c.data("fineuploader");return b?(void 0===d&&(d={}),d[a]=b,c.data("fineuploader",d),void 0):void 0===d?null:d[a]},b=function(a){return e("uploader",a)},f=function(a,b){return e(a,b)},h=function(b){var d=b.callbacks={},e=new qq.FineUploaderBasic;a.each(e._options.callbacks,function(a){var b,e;b=/^on(\w+)/.exec(a)[1],b=b.substring(0,1).toLowerCase()+b.substring(1),e=c,d[a]=function(){var a=Array.prototype.slice.call(arguments);return e.triggerHandler(b,a)}})},i=function(b,d){var e,h;return e=void 0===d?"basic"!==b.uploaderType?{element:c[0]}:{}:d,a.each(b,function(b,c){a.inArray(b,g)>=0?f(b,c):c instanceof a?e[b]=c[0]:a.isPlainObject(c)?(e[b]={},i(c,e[b])):a.isArray(c)?(h=[],a.each(c,function(b,c){c instanceof a?a.merge(h,c):h.push(c)}),e[b]=h):e[b]=c}),void 0===d?e:void 0},j=function(c){return"string"===a.type(c)&&!c.match(/^_/)&&void 0!==b()[c]},k=function(c){var d,e=[],f=Array.prototype.slice.call(arguments,1);return i(f,e),d=b()[c].apply(b(),e),"object"!=typeof d||1!==d.nodeType&&9!==d.nodeType||!d.cloneNode||(d=a(d)),d},a.fn.fineUploader=function(e){var f=this,g=arguments,h=[];return this.each(function(i,l){if(c=a(l),b()&&j(e)){if(h.push(k.apply(f,g)),1===f.length)return!1}else"object"!=typeof e&&e?a.error("Method "+e+" does not exist on jQuery.fineUploader"):d.apply(f,g)}),1===h.length?h[0]:h.length>1?h:this}}(jQuery),function(a){"use strict";function b(a){a||(a={}),a.dropZoneElements=[i];var b=f(a);return e(b),d(new qq.DragAndDrop(b)),i}function c(a,b){var c=i.data(j);return b?(void 0===c&&(c={}),c[a]=b,i.data(j,c),void 0):void 0===c?null:c[a]}function d(a){return c("dndInstance",a)}function e(b){var c=b.callbacks={};new qq.FineUploaderBasic,a.each(new qq.DragAndDrop.callbacks,function(a){var b,d=a;b=i,c[a]=function(){var a=Array.prototype.slice.call(arguments),c=b.triggerHandler(d,a);return c}})}function f(b,c){var d,e;return d=void 0===c?{}:c,a.each(b,function(b,c){c instanceof a?d[b]=c[0]:a.isPlainObject(c)?(d[b]={},f(c,d[b])):a.isArray(c)?(e=[],a.each(c,function(b,c){c instanceof a?a.merge(e,c):e.push(c)}),d[b]=e):d[b]=c}),void 0===c?d:void 0}function g(b){return"string"===a.type(b)&&"dispose"===b&&void 0!==d()[b]}function h(a){var b=[],c=Array.prototype.slice.call(arguments,1);return f(c,b),d()[a].apply(d(),b)}var i,j="fineUploaderDnd";a.fn.fineUploaderDnd=function(c){var e=this,f=arguments,j=[];return this.each(function(k,l){if(i=a(l),d()&&g(c)){if(j.push(h.apply(e,f)),1===e.length)return!1}else"object"!=typeof c&&c?a.error("Method "+c+" does not exist in Fine Uploader's DnD module."):b.apply(e,f)}),1===j.length?j[0]:j.length>1?j:this}}(jQuery);
+/*! 2013-07-16 */
diff --git a/ajax/libs/file-uploader/3.7.0/fineuploader.css b/ajax/libs/file-uploader/3.7.0/fineuploader.css
new file mode 100644
index 000000000..099fe45b8
--- /dev/null
+++ b/ajax/libs/file-uploader/3.7.0/fineuploader.css
@@ -0,0 +1,199 @@
+/*!
+ * Fine Uploader
+ *
+ * Copyright 2013, Widen Enterprises, Inc. info@fineuploader.com
+ *
+ * Version: 3.7.0
+ *
+ * Homepage: http://fineuploader.com
+ *
+ * Repository: git://github.com/Widen/fine-uploader.git
+ *
+ * Licensed under GNU GPL v3, see LICENSE
+ */
+
+
+.qq-uploader {
+ position: relative;
+ width: 100%;
+}
+.qq-upload-button {
+ display: block;
+ width: 105px;
+ padding: 7px 0;
+ text-align: center;
+ background: #880000;
+ border-bottom: 1px solid #DDD;
+ color: #FFF;
+}
+.qq-upload-button-hover {
+ background: #CC0000;
+}
+.qq-upload-button-focus {
+ outline: 1px dotted #000000;
+}
+.qq-upload-drop-area, .qq-upload-extra-drop-area {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ min-height: 30px;
+ z-index: 2;
+ background: #FF9797;
+ text-align: center;
+}
+.qq-upload-drop-area span {
+ display: block;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ margin-top: -8px;
+ font-size: 16px;
+}
+.qq-upload-extra-drop-area {
+ position: relative;
+ margin-top: 50px;
+ font-size: 16px;
+ padding-top: 30px;
+ height: 20px;
+ min-height: 40px;
+}
+.qq-upload-drop-area-active {
+ background: #FF7171;
+}
+.qq-upload-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+.qq-upload-list li {
+ margin: 0;
+ padding: 9px;
+ line-height: 15px;
+ font-size: 16px;
+ background-color: #FFF0BD;
+}
+.qq-upload-file, .qq-upload-spinner, .qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text, .qq-upload-finished, .qq-upload-delete {
+ margin-right: 12px;
+}
+.qq-upload-file {
+}
+.qq-upload-spinner {
+ display: inline-block;
+ background: url("loading.gif");
+ width: 15px;
+ height: 15px;
+ vertical-align: text-bottom;
+}
+.qq-drop-processing {
+ display: none;
+}
+.qq-drop-processing-spinner {
+ display: inline-block;
+ background: url("processing.gif");
+ width: 24px;
+ height: 24px;
+ vertical-align: text-bottom;
+}
+.qq-upload-finished {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-retry, .qq-upload-delete {
+ display: none;
+ color: #000000;
+}
+.qq-upload-cancel, .qq-upload-delete {
+ color: #000000;
+}
+.qq-upload-retryable .qq-upload-retry {
+ display: inline;
+}
+.qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-delete {
+ font-size: 12px;
+ font-weight: normal;
+}
+.qq-upload-failed-text {
+ display: none;
+ font-style: italic;
+ font-weight: bold;
+}
+.qq-upload-failed-icon {
+ display:none;
+ width:15px;
+ height:15px;
+ vertical-align:text-bottom;
+}
+.qq-upload-fail .qq-upload-failed-text {
+ display: inline;
+}
+.qq-upload-retrying .qq-upload-failed-text {
+ display: inline;
+ color: #D60000;
+}
+.qq-upload-list li.qq-upload-success {
+ background-color: #5DA30C;
+ color: #FFFFFF;
+}
+.qq-upload-list li.qq-upload-fail {
+ background-color: #D60000;
+ color: #FFFFFF;
+}
+.qq-progress-bar {
+ background: -moz-linear-gradient(top, rgba(30,87,153,1) 0%, rgba(41,137,216,1) 50%, rgba(32,124,202,1) 51%, rgba(125,185,232,1) 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(30,87,153,1)), color-stop(50%,rgba(41,137,216,1)), color-stop(51%,rgba(32,124,202,1)), color-stop(100%,rgba(125,185,232,1))); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* IE10+ */
+ background: linear-gradient(to bottom, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* W3C */
+ width: 0%;
+ height: 15px;
+ border-radius: 6px;
+ margin-bottom: 3px;
+ display: none;
+}
+
+INPUT.qq-edit-filename {
+ position: absolute;
+ opacity: 0;
+ filter: alpha(opacity=0);
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
+}
+
+.qq-upload-file.qq-editable {
+ cursor: pointer;
+}
+
+.qq-edit-filename-icon.qq-editable {
+ display: inline-block;
+ cursor: pointer;
+}
+
+INPUT.qq-edit-filename.qq-editing {
+ position: static;
+ margin-top: -5px;
+ margin-right: 10px;
+ margin-bottom: -5px;
+
+ opacity: 1;
+ filter: alpha(opacity=100);
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
+}
+
+.qq-edit-filename-icon {
+ display: none;
+ background: url("edit.gif");
+ width: 15px;
+ height: 15px;
+ vertical-align: text-bottom;
+ margin-right: 5px;
+}
+
+INPUT.qq-edit-filename.qq-editing ~ .qq-upload-cancel {
+ display: none;
+}
+
+/*! 2013-07-16 */
diff --git a/ajax/libs/file-uploader/3.7.0/fineuploader.js b/ajax/libs/file-uploader/3.7.0/fineuploader.js
new file mode 100644
index 000000000..6b417c6a5
--- /dev/null
+++ b/ajax/libs/file-uploader/3.7.0/fineuploader.js
@@ -0,0 +1,5480 @@
+/*!
+ * Fine Uploader
+ *
+ * Copyright 2013, Widen Enterprises, Inc. info@fineuploader.com
+ *
+ * Version: 3.7.0
+ *
+ * Homepage: http://fineuploader.com
+ *
+ * Repository: git://github.com/Widen/fine-uploader.git
+ *
+ * Licensed under GNU GPL v3, see LICENSE
+ */
+
+
+/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob*/
+var qq = function(element) {
+ "use strict";
+
+ return {
+ hide: function() {
+ element.style.display = 'none';
+ return this;
+ },
+
+ /** Returns the function which detaches attached event */
+ attach: function(type, fn) {
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+ return function() {
+ qq(element).detach(type, fn);
+ };
+ },
+
+ detach: function(type, fn) {
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+ return this;
+ },
+
+ contains: function(descendant) {
+ // The [W3C spec](http://www.w3.org/TR/domcore/#dom-node-contains)
+ // says a `null` (or ostensibly `undefined`) parameter
+ // passed into `Node.contains` should result in a false return value.
+ // IE7 throws an exception if the parameter is `undefined` though.
+ if (!descendant) {
+ return false;
+ }
+
+ // compareposition returns false in this case
+ if (element === descendant) {
+ return true;
+ }
+
+ if (element.contains){
+ return element.contains(descendant);
+ } else {
+ /*jslint bitwise: true*/
+ return !!(descendant.compareDocumentPosition(element) & 8);
+ }
+ },
+
+ /**
+ * Insert this element before elementB.
+ */
+ insertBefore: function(elementB) {
+ elementB.parentNode.insertBefore(element, elementB);
+ return this;
+ },
+
+ remove: function() {
+ element.parentNode.removeChild(element);
+ return this;
+ },
+
+ /**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+ css: function(styles) {
+ if (styles.opacity != null){
+ if (typeof element.style.opacity !== 'string' && typeof(element.filters) !== 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+
+ return this;
+ },
+
+ hasClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+ },
+
+ addClass: function(name) {
+ if (!qq(element).hasClass(name)){
+ element.className += ' ' + name;
+ }
+ return this;
+ },
+
+ removeClass: function(name) {
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+ return this;
+ },
+
+ getByClass: function(className) {
+ var candidates,
+ result = [];
+
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ candidates = element.getElementsByTagName("*");
+
+ qq.each(candidates, function(idx, val) {
+ if (qq(val).hasClass(className)){
+ result.push(val);
+ }
+ });
+ return result;
+ },
+
+ children: function() {
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType === 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+ },
+
+ setText: function(text) {
+ element.innerText = text;
+ element.textContent = text;
+ return this;
+ },
+
+ clearText: function() {
+ return qq(element).setText("");
+ }
+ };
+};
+
+qq.log = function(message, level) {
+ "use strict";
+
+ if (window.console) {
+ if (!level || level === 'info') {
+ window.console.log(message);
+ }
+ else
+ {
+ if (window.console[level]) {
+ window.console[level](message);
+ }
+ else {
+ window.console.log('<' + level + '> ' + message);
+ }
+ }
+ }
+};
+
+qq.isObject = function(variable) {
+ "use strict";
+ return variable && !variable.nodeType && Object.prototype.toString.call(variable) === '[object Object]';
+};
+
+qq.isFunction = function(variable) {
+ "use strict";
+ return typeof(variable) === "function";
+};
+
+qq.isArray = function(variable) {
+ "use strict";
+ return Object.prototype.toString.call(variable) === "[object Array]";
+}
+
+qq.isString = function(maybeString) {
+ "use strict";
+ return Object.prototype.toString.call(maybeString) === '[object String]';
+};
+
+qq.trimStr = function(string) {
+ if (String.prototype.trim) {
+ return string.trim();
+ }
+
+ return string.replace(/^\s+|\s+$/g,'');
+};
+
+
+// Returns a string, swapping argument values with the associated occurrence of {} in the passed string.
+qq.format = function(str) {
+ "use strict";
+
+ var args = Array.prototype.slice.call(arguments, 1),
+ newStr = str;
+
+ qq.each(args, function(idx, val) {
+ newStr = newStr.replace(/{}/, val);
+ });
+
+ return newStr;
+};
+
+qq.isFile = function(maybeFile) {
+ "use strict";
+
+ return window.File && Object.prototype.toString.call(maybeFile) === '[object File]'
+};
+
+qq.isFileList = function(maybeFileList) {
+ return window.FileList && Object.prototype.toString.call(maybeFileList) === '[object FileList]'
+}
+
+qq.isFileOrInput = function(maybeFileOrInput) {
+ "use strict";
+
+ return qq.isFile(maybeFileOrInput) || qq.isInput(maybeFileOrInput);
+};
+
+qq.isInput = function(maybeInput) {
+ if (window.HTMLInputElement) {
+ if (Object.prototype.toString.call(maybeInput) === '[object HTMLInputElement]') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+ if (maybeInput.tagName) {
+ if (maybeInput.tagName.toLowerCase() === 'input') {
+ if (maybeInput.type && maybeInput.type.toLowerCase() === 'file') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+qq.isBlob = function(maybeBlob) {
+ "use strict";
+ return window.Blob && Object.prototype.toString.call(maybeBlob) === '[object Blob]';
+};
+
+qq.isXhrUploadSupported = function() {
+ "use strict";
+ var input = document.createElement('input');
+ input.type = 'file';
+
+ return (
+ input.multiple !== undefined &&
+ typeof File !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ typeof (new XMLHttpRequest()).upload !== "undefined" );
+};
+
+qq.isFolderDropSupported = function(dataTransfer) {
+ "use strict";
+ return (dataTransfer.items && dataTransfer.items[0].webkitGetAsEntry);
+};
+
+qq.isFileChunkingSupported = function() {
+ "use strict";
+ return !qq.android() && //android's impl of Blob.slice is broken
+ qq.isXhrUploadSupported() &&
+ (File.prototype.slice !== undefined || File.prototype.webkitSlice !== undefined || File.prototype.mozSlice !== undefined);
+};
+
+qq.extend = function (first, second, extendNested) {
+ "use strict";
+
+ qq.each(second, function(prop, val) {
+ if (extendNested && qq.isObject(val)) {
+ if (first[prop] === undefined) {
+ first[prop] = {};
+ }
+ qq.extend(first[prop], val, true);
+ }
+ else {
+ first[prop] = val;
+ }
+ });
+
+ return first;
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ "use strict";
+
+ if (arr.indexOf) {
+ return arr.indexOf(elt, from);
+ }
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from+=1){
+ if (arr.hasOwnProperty(from) && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+//this is a version 4 UUID
+qq.getUniqueId = function(){
+ "use strict";
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ /*jslint eqeq: true, bitwise: true*/
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+};
+
+//
+// Browsers and platforms detection
+
+qq.ie = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE') !== -1;
+};
+qq.ie10 = function(){
+ "use strict";
+ return navigator.userAgent.indexOf('MSIE 10') !== -1;
+};
+qq.safari = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf("Apple") !== -1;
+};
+qq.chrome = function(){
+ "use strict";
+ return navigator.vendor !== undefined && navigator.vendor.indexOf('Google') !== -1;
+};
+qq.firefox = function(){
+ "use strict";
+ return (navigator.userAgent.indexOf('Mozilla') !== -1 && navigator.vendor !== undefined && navigator.vendor === '');
+};
+qq.windows = function(){
+ "use strict";
+ return navigator.platform === "Win32";
+};
+qq.android = function(){
+ "use strict";
+ return navigator.userAgent.toLowerCase().indexOf('android') !== -1;
+};
+qq.ios = function() {
+ "use strict";
+ return navigator.userAgent.indexOf("iPad") !== -1
+ || navigator.userAgent.indexOf("iPod") !== -1
+ || navigator.userAgent.indexOf("iPhone") !== -1;
+};
+
+//
+// Events
+
+qq.preventDefault = function(e){
+ "use strict";
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ "use strict";
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+}());
+
+//key and value are passed to callback for each item in the object or array
+qq.each = function(objOrArray, callback) {
+ "use strict";
+ var keyOrIndex, retVal;
+ if (objOrArray) {
+ if (qq.isArray(objOrArray)) {
+ for (keyOrIndex = 0; keyOrIndex < objOrArray.length; keyOrIndex++) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ else {
+ for (keyOrIndex in objOrArray) {
+ if (Object.prototype.hasOwnProperty.call(objOrArray, keyOrIndex)) {
+ retVal = callback(keyOrIndex, objOrArray[keyOrIndex]);
+ if (retVal === false) {
+ break;
+ }
+ }
+ }
+ }
+ }
+};
+
+//include any args that should be passed to the new function after the context arg
+qq.bind = function(oldFunc, context) {
+ if (qq.isFunction(oldFunc)) {
+ var args = Array.prototype.slice.call(arguments, 2);
+
+ return function() {
+ if (arguments.length) {
+ args = args.concat(Array.prototype.slice.call(arguments))
+ }
+ return oldFunc.apply(context, args);
+ };
+ }
+
+ throw new Error("first parameter must be a function!");
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ "use strict";
+ /*jshint laxbreak: true*/
+ var i, len,
+ uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp !== 'undefined') && (i !== 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj !== 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (i = -1, len = obj.length; i < len; i+=1){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj !== 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (i in obj){
+ if (obj.hasOwnProperty(i)) {
+ add(obj[i], i);
+ }
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ if (temp) {
+ return uristrings.join(prefix);
+ } else {
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+ }
+};
+
+qq.obj2FormData = function(obj, formData, arrayKeyName) {
+ "use strict";
+ if (!formData) {
+ formData = new FormData();
+ }
+
+ qq.each(obj, function(key, val) {
+ key = arrayKeyName ? arrayKeyName + '[' + key + ']' : key;
+
+ if (qq.isObject(val)) {
+ qq.obj2FormData(val, formData, key);
+ }
+ else if (qq.isFunction(val)) {
+ formData.append(key, val());
+ }
+ else {
+ formData.append(key, val);
+ }
+ });
+
+ return formData;
+};
+
+qq.obj2Inputs = function(obj, form) {
+ "use strict";
+ var input;
+
+ if (!form) {
+ form = document.createElement('form');
+ }
+
+ qq.obj2FormData(obj, {
+ append: function(key, val) {
+ input = document.createElement('input');
+ input.setAttribute('name', key);
+ input.setAttribute('value', val);
+ form.appendChild(input);
+ }
+ });
+
+ return form;
+};
+
+qq.setCookie = function(name, value, days) {
+ var date = new Date(),
+ expires = "";
+
+ if (days) {
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+qq.getCookie = function(name) {
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';'),
+ cookie;
+
+ qq.each(ca, function(idx, part) {
+ var cookiePart = part;
+ while (cookiePart.charAt(0)==' ') {
+ cookiePart = cookiePart.substring(1, cookiePart.length);
+ }
+
+ if (cookiePart.indexOf(nameEQ) === 0) {
+ cookie = cookiePart.substring(nameEQ.length, cookiePart.length);
+ return false;
+ }
+ });
+
+ return cookie;
+};
+
+qq.getCookieNames = function(regexp) {
+ var cookies = document.cookie.split(';'),
+ cookieNames = [];
+
+ qq.each(cookies, function(idx, cookie) {
+ cookie = qq.trimStr(cookie);
+
+ var equalsIdx = cookie.indexOf("=");
+
+ if (cookie.match(regexp)) {
+ cookieNames.push(cookie.substr(0, equalsIdx));
+ }
+ });
+
+ return cookieNames;
+};
+
+qq.deleteCookie = function(name) {
+ qq.setCookie(name, "", -1);
+};
+
+qq.areCookiesEnabled = function() {
+ var randNum = Math.random() * 100000,
+ name = "qqCookieTest:" + randNum;
+ qq.setCookie(name, 1);
+
+ if (qq.getCookie(name)) {
+ qq.deleteCookie(name);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not
+ * implemented. For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.
+ */
+qq.parseJson = function(json) {
+ /*jshint evil: true*/
+ if (window.JSON && qq.isFunction(JSON.parse)) {
+ return JSON.parse(json);
+ } else {
+ return eval("(" + json + ")");
+ }
+};
+
+/**
+ * A generic module which supports object disposing in dispose() method.
+ * */
+qq.DisposeSupport = function() {
+ "use strict";
+ var disposers = [];
+
+ return {
+ /** Run all registered disposers */
+ dispose: function() {
+ var disposer;
+ do {
+ disposer = disposers.shift();
+ if (disposer) {
+ disposer();
+ }
+ }
+ while (disposer);
+ },
+
+ /** Attach event handler and register de-attacher as a disposer */
+ attach: function() {
+ var args = arguments;
+ /*jslint undef:true*/
+ this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
+ },
+
+ /** Add disposer to the collection */
+ addDisposer: function(disposeFunction) {
+ disposers.push(disposeFunction);
+ }
+ };
+};
+;qq.version="3.7.0";;qq.supportedFeatures = (function () {
+ var supportsUploading,
+ supportsAjaxFileUploading,
+ supportsFolderDrop,
+ supportsChunking,
+ supportsResume,
+ supportsUploadViaPaste,
+ supportsUploadCors,
+ supportsDeleteFileXdr,
+ supportsDeleteFileCorsXhr,
+ supportsDeleteFileCors;
+
+
+ function testSupportsFileInputElement() {
+ var supported = true,
+ tempInput;
+
+ try {
+ tempInput = document.createElement('input');
+ tempInput.type = 'file';
+ qq(tempInput).hide();
+
+ if (tempInput.disabled) {
+ supported = false;
+ }
+ }
+ catch (ex) {
+ supported = false;
+ }
+
+ return supported;
+ }
+
+ //only way to test for Filesystem API support since webkit does not expose the DataTransfer interface
+ function isChrome21OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[2][1-9]|Chrome\/[3-9][0-9]/) !== undefined;
+ }
+
+ //only way to test for complete Clipboard API support at this time
+ function isChrome14OrHigher() {
+ return qq.chrome() &&
+ navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/) !== undefined;
+ }
+
+ //Ensure we can send cross-origin `XMLHttpRequest`s
+ function isCrossOriginXhrSupported() {
+ if (window.XMLHttpRequest) {
+ var xhr = new XMLHttpRequest();
+
+ //Commonly accepted test for XHR CORS support.
+ return xhr.withCredentials !== undefined;
+ }
+
+ return false;
+ }
+
+ //Test for (terrible) cross-origin ajax transport fallback for IE9 and IE8
+ function isXdrSupported() {
+ return window.XDomainRequest !== undefined;
+ }
+
+ // CORS Ajax requests are supported if it is either possible to send credentialed `XMLHttpRequest`s,
+ // or if `XDomainRequest` is an available alternative.
+ function isCrossOriginAjaxSupported() {
+ if (isCrossOriginXhrSupported()) {
+ return true;
+ }
+
+ return isXdrSupported();
+ }
+
+
+ supportsUploading = testSupportsFileInputElement();
+
+ supportsAjaxFileUploading = supportsUploading && qq.isXhrUploadSupported();
+
+ supportsFolderDrop = supportsAjaxFileUploading && isChrome21OrHigher();
+
+ supportsChunking = supportsAjaxFileUploading && qq.isFileChunkingSupported();
+
+ supportsResume = supportsAjaxFileUploading && supportsChunking && qq.areCookiesEnabled();
+
+ supportsUploadViaPaste = supportsAjaxFileUploading && isChrome14OrHigher();
+
+ supportsUploadCors = supportsUploading && (window.postMessage !== undefined || supportsAjaxFileUploading);
+
+ supportsDeleteFileCorsXhr = isCrossOriginXhrSupported();
+
+ supportsDeleteFileXdr = isXdrSupported();
+
+ supportsDeleteFileCors = isCrossOriginAjaxSupported();
+
+
+ return {
+ uploading: supportsUploading,
+ ajaxUploading: supportsAjaxFileUploading,
+ fileDrop: supportsAjaxFileUploading, //NOTE: will also return true for touch-only devices. It's not currently possible to accurately test for touch-only devices
+ folderDrop: supportsFolderDrop,
+ chunking: supportsChunking,
+ resume: supportsResume,
+ uploadCustomHeaders: supportsAjaxFileUploading,
+ uploadNonMultipart: supportsAjaxFileUploading,
+ itemSizeValidation: supportsAjaxFileUploading,
+ uploadViaPaste: supportsUploadViaPaste,
+ progressBar: supportsAjaxFileUploading,
+ uploadCors: supportsUploadCors,
+ deleteFileCorsXhr: supportsDeleteFileCorsXhr,
+ deleteFileCorsXdr: supportsDeleteFileXdr, //NOTE: will also return true in IE10, where XDR is also supported
+ deleteFileCors: supportsDeleteFileCors,
+ canDetermineSize: supportsAjaxFileUploading
+ }
+
+}());
+;/*globals qq*/
+qq.Promise = function() {
+ "use strict";
+
+ var successValue, failureValue,
+ successCallbacks = [],
+ failureCallbacks = [],
+ doneCallbacks = [],
+ state = 0;
+
+ return {
+ then: function(onSuccess, onFailure) {
+ if (state === 0) {
+ if (onSuccess) {
+ successCallbacks.push(onSuccess);
+ }
+ if (onFailure) {
+ failureCallbacks.push(onFailure);
+ }
+ }
+ else if (state === -1 && onFailure) {
+ onFailure(failureValue);
+ }
+ else if (onSuccess) {
+ onSuccess(successValue);
+ }
+
+ return this;
+ },
+
+ done: function(callback) {
+ if (state === 0) {
+ doneCallbacks.push(callback);
+ }
+ else {
+ callback();
+ }
+
+ return this;
+ },
+
+ success: function(val) {
+ state = 1;
+ successValue = val;
+
+ if (successCallbacks.length) {
+ qq.each(successCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ },
+
+ failure: function(val) {
+ state = -1;
+ failureValue = val;
+
+ if (failureCallbacks.length) {
+ qq.each(failureCallbacks, function(idx, callback) {
+ callback(val);
+ })
+ }
+
+ if(doneCallbacks.length) {
+ qq.each(doneCallbacks, function(idx, callback) {
+ callback();
+ })
+ }
+
+ return this;
+ }
+ };
+};
+
+qq.isPromise = function(maybePromise) {
+ return maybePromise && maybePromise.then && maybePromise.done;
+};;/*globals qq*/
+
+/**
+ * This module represents an upload or "Select File(s)" button. It's job is to embed an opaque ` `
+ * element as a child of a provided "container" element. This "container" element (`options.element`) is used to provide
+ * a custom style for the ` ` element. The ability to change the style of the container element is also
+ * provided here by adding CSS classes to the container on hover/focus.
+ *
+ * TODO Eliminate the mouseover and mouseout event handlers since the :hover CSS pseudo-class should now be
+ * available on all supported browsers.
+ *
+ * @param o Options to override the default values
+ */
+qq.UploadButton = function(o) {
+ "use strict";
+
+ var input,
+ // Used to detach all event handlers created at once for this instance
+ disposeSupport = new qq.DisposeSupport(),
+
+ options = {
+ // "Container" element
+ element: null,
+
+ // If true adds `multiple` attribute to ` `
+ multiple: false,
+
+ // Corresponds to the `accept` attribute on the associated ` `
+
+ acceptFiles: null,
+
+ // `name` attribute of ` `
+ name: 'qqfile',
+
+ // Called when the browser invokes the onchange handler on the ` `
+ onChange: function(input) {},
+
+ // **This option will be removed** in the future as the :hover CSS pseudo-class is available on all supported browsers
+ hoverClass: 'qq-upload-button-hover',
+
+ focusClass: 'qq-upload-button-focus'
+ };
+
+ // Overrides any of the default option values with any option values passed in during construction.
+ qq.extend(options, o);
+
+
+ // Embed an opaque ` ` element as a child of `options.element`.
+ function createInput() {
+ var input = document.createElement("input");
+
+ if (options.multiple){
+ input.setAttribute("multiple", "multiple");
+ }
+
+ if (options.acceptFiles) {
+ input.setAttribute("accept", options.acceptFiles);
+ }
+
+ input.setAttribute("type", "file");
+ input.setAttribute("name", options.name);
+
+ qq(input).css({
+ position: 'absolute',
+ // in Opera only 'browse' button
+ // is clickable and it is located at
+ // the right side of the input
+ right: 0,
+ top: 0,
+ fontFamily: 'Arial',
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+ fontSize: '118px',
+ margin: 0,
+ padding: 0,
+ cursor: 'pointer',
+ opacity: 0
+ });
+
+ options.element.appendChild(input);
+
+ disposeSupport.attach(input, 'change', function(){
+ options.onChange(input);
+ });
+
+ // **These event handlers will be removed** in the future as the :hover CSS pseudo-class is available on all supported browsers
+ disposeSupport.attach(input, 'mouseover', function(){
+ qq(options.element).addClass(options.hoverClass);
+ });
+ disposeSupport.attach(input, 'mouseout', function(){
+ qq(options.element).removeClass(options.hoverClass);
+ });
+
+ disposeSupport.attach(input, 'focus', function(){
+ qq(options.element).addClass(options.focusClass);
+ });
+ disposeSupport.attach(input, 'blur', function(){
+ qq(options.element).removeClass(options.focusClass);
+ });
+
+ // IE and Opera, unfortunately have 2 tab stops on file input
+ // which is unacceptable in our case, disable keyboard access
+ if (window.attachEvent) {
+ // it is IE or Opera
+ input.setAttribute('tabIndex', "-1");
+ }
+
+ return input;
+ }
+
+ // Make button suitable container for input
+ qq(options.element).css({
+ position: 'relative',
+ overflow: 'hidden',
+ // Make sure browse button is in the right side in Internet Explorer
+ direction: 'ltr'
+ });
+
+ input = createInput();
+
+
+ // Exposed API
+ return {
+ getInput: function(){
+ return input;
+ },
+
+ reset: function(){
+ if (input.parentNode){
+ qq(input).remove();
+ }
+
+ qq(options.element).removeClass(options.focusClass);
+ input = createInput();
+ }
+ };
+};
+;/*globals qq*/
+qq.PasteSupport = function(o) {
+ "use strict";
+
+ var options, detachPasteHandler;
+
+ options = {
+ targetElement: null,
+ callbacks: {
+ log: function(message, level) {},
+ pasteReceived: function(blob) {}
+ }
+ };
+
+ function isImage(item) {
+ return item.type &&
+ item.type.indexOf("image/") === 0;
+ }
+
+ function registerPasteHandler() {
+ qq(options.targetElement).attach("paste", function(event) {
+ var clipboardData = event.clipboardData;
+
+ if (clipboardData) {
+ qq.each(clipboardData.items, function(idx, item) {
+ if (isImage(item)) {
+ var blob = item.getAsFile();
+ options.callbacks.pasteReceived(blob);
+ }
+ });
+ }
+ });
+ }
+
+ function unregisterPasteHandler() {
+ if (detachPasteHandler) {
+ detachPasteHandler();
+ }
+ }
+
+ qq.extend(options, o);
+ registerPasteHandler();
+
+ return {
+ reset: function() {
+ unregisterPasteHandler();
+ }
+ };
+};;qq.UploadData = function(uploaderProxy) {
+ var data = [],
+ byId = {},
+ byUuid = {},
+ byStatus = {},
+ api;
+
+ function getDataByIds(ids) {
+ if (qq.isArray(ids)) {
+ var entries = [];
+
+ qq.each(ids, function(idx, id) {
+ entries.push(data[byId[id]]);
+ });
+
+ return entries;
+ }
+
+ return data[byId[ids]];
+ }
+
+ function getDataByUuids(uuids) {
+ if (qq.isArray(uuids)) {
+ var entries = [];
+
+ qq.each(uuids, function(idx, uuid) {
+ entries.push(data[byUuid[uuid]]);
+ });
+
+ return entries;
+ }
+
+ return data[byUuid[uuids]];
+ }
+
+ function getDataByStatus(status) {
+ var statusResults = [],
+ statuses = [].concat(status);
+
+ qq.each(statuses, function(index, statusEnum) {
+ var statusResultIndexes = byStatus[statusEnum];
+
+ if (statusResultIndexes !== undefined) {
+ qq.each(statusResultIndexes, function(i, dataIndex) {
+ statusResults.push(data[dataIndex]);
+ });
+ }
+ });
+
+ return statusResults;
+ }
+
+ api = {
+ added: function(id) {
+ var uuid = uploaderProxy.getUuid(id),
+ name = uploaderProxy.getName(id),
+ size = uploaderProxy.getSize(id),
+ status = qq.status.SUBMITTING;
+
+ var index = data.push({
+ id: id,
+ name: name,
+ originalName: name,
+ uuid: uuid,
+ size: size,
+ status: status
+ }) - 1;
+
+ byId[id] = index;
+
+ byUuid[uuid] = index;
+
+ if (byStatus[status] === undefined) {
+ byStatus[status] = [];
+ }
+ byStatus[status].push(index);
+
+ uploaderProxy.onStatusChange(id, undefined, status);
+ },
+
+ retrieve: function(optionalFilter) {
+ if (qq.isObject(optionalFilter) && data.length) {
+ if (optionalFilter.id !== undefined) {
+ return getDataByIds(optionalFilter.id);
+ }
+
+ else if (optionalFilter.uuid !== undefined) {
+ return getDataByUuids(optionalFilter.uuid);
+ }
+
+ else if (optionalFilter.status) {
+ return getDataByStatus(optionalFilter.status);
+ }
+ }
+ else {
+ return qq.extend([], data, true);
+ }
+ },
+
+ reset: function() {
+ data = [];
+ byId = {};
+ byUuid = {};
+ byStatus = {};
+ },
+
+ setStatus: function(id, newStatus) {
+ var dataIndex = byId[id],
+ oldStatus = data[dataIndex].status,
+ byStatusOldStatusIndex = qq.indexOf(byStatus[oldStatus], dataIndex);
+
+ byStatus[oldStatus].splice(byStatusOldStatusIndex, 1);
+
+ data[dataIndex].status = newStatus;
+
+ if (byStatus[newStatus] === undefined) {
+ byStatus[newStatus] = [];
+ }
+ byStatus[newStatus].push(dataIndex);
+
+ uploaderProxy.onStatusChange(id, oldStatus, newStatus);
+ },
+
+ uuidChanged: function(id, newUuid) {
+ var dataIndex = byId[id],
+ oldUuid = data[dataIndex].uuid;
+
+ data[dataIndex].uuid = newUuid;
+ byUuid[newUuid] = dataIndex;
+ delete byUuid[oldUuid];
+ },
+
+ nameChanged: function(id, newName) {
+ var dataIndex = byId[id];
+
+ data[dataIndex].name = newName;
+ }
+ };
+
+ return api;
+};
+
+qq.status = {
+ SUBMITTING: "submitting",
+ SUBMITTED: "submitted",
+ REJECTED: "rejected",
+ QUEUED: "queued",
+ CANCELED: "canceled",
+ UPLOADING: "uploading",
+ UPLOAD_RETRYING: "retrying upload",
+ UPLOAD_SUCCESSFUL: "upload successful",
+ UPLOAD_FAILED: "upload failed",
+ DELETE_FAILED: "delete failed",
+ DELETING: "deleting",
+ DELETED: "deleted"
+};
+;qq.FineUploaderBasic = function(o) {
+ this._options = {
+ debug: false,
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ disableCancelForFormUploads: false,
+ autoUpload: true,
+ request: {
+ endpoint: '/server/upload',
+ params: {},
+ paramsInBody: true,
+ customHeaders: {},
+ forceMultipart: true,
+ inputName: 'qqfile',
+ uuidName: 'qquuid',
+ totalFileSizeName: 'qqtotalfilesize',
+ filenameParam: 'qqfilename'
+ },
+ validation: {
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ itemLimit: 0,
+ stopOnFirstInvalidFile: true,
+ acceptFiles: null
+ },
+ callbacks: {
+ onSubmit: function(id, name){},
+ onSubmitted: function(id, name){},
+ onComplete: function(id, name, responseJSON, maybeXhr){},
+ onCancel: function(id, name){},
+ onUpload: function(id, name){},
+ onUploadChunk: function(id, name, chunkData){},
+ onResume: function(id, fileName, chunkData){},
+ onProgress: function(id, name, loaded, total){},
+ onError: function(id, name, reason, maybeXhrOrXdr) {},
+ onAutoRetry: function(id, name, attemptNumber) {},
+ onManualRetry: function(id, name) {},
+ onValidateBatch: function(fileOrBlobData) {},
+ onValidate: function(fileOrBlobData) {},
+ onSubmitDelete: function(id) {},
+ onDelete: function(id){},
+ onDeleteComplete: function(id, xhrOrXdr, isError){},
+ onPasteReceived: function(blob) {},
+ onStatusChange: function(id, oldStatus, newStatus) {}
+ },
+ messages: {
+ typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ noFilesError: "No files to upload.",
+ tooManyItemsError: "Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",
+ retryFailTooManyItems: "Retry failed - you have reached your file limit.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ retry: {
+ enableAuto: false,
+ maxAutoAttempts: 3,
+ autoAttemptDelay: 5,
+ preventRetryResponseProperty: 'preventRetry'
+ },
+ classes: {
+ buttonHover: 'qq-upload-button-hover',
+ buttonFocus: 'qq-upload-button-focus'
+ },
+ chunking: {
+ enabled: false,
+ partSize: 2000000,
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalFileSize: 'qqtotalfilesize',
+ totalParts: 'qqtotalparts'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ formatFileName: function(fileOrBlobName) {
+ if (fileOrBlobName !== undefined && fileOrBlobName.length > 33) {
+ fileOrBlobName = fileOrBlobName.slice(0, 19) + '...' + fileOrBlobName.slice(-14);
+ }
+ return fileOrBlobName;
+ },
+ text: {
+ defaultResponseError: "Upload failure reason unknown",
+ sizeSymbols: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB']
+ },
+ deleteFile : {
+ enabled: false,
+ method: "DELETE",
+ endpoint: '/server/upload',
+ customHeaders: {},
+ params: {}
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false,
+ allowXdr: false
+ },
+ blobs: {
+ defaultName: 'misc_data'
+ },
+ paste: {
+ targetElement: null,
+ defaultName: 'pasted_image'
+ },
+ camera: {
+ ios: false
+ }
+ };
+
+ qq.extend(this._options, o, true);
+
+ this._handleCameraAccess();
+
+ this._wrapCallbacks();
+ this._disposeSupport = new qq.DisposeSupport();
+
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData = this._createUploadDataTracker();
+
+ this._paramsStore = this._createParamsStore("request");
+ this._deleteFileParamsStore = this._createParamsStore("deleteFile");
+
+ this._endpointStore = this._createEndpointStore("request");
+ this._deleteFileEndpointStore = this._createEndpointStore("deleteFile");
+
+ this._handler = this._createUploadHandler();
+ this._deleteHandler = this._createDeleteHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ if (this._options.paste.targetElement) {
+ this._pasteHandler = this._createPasteHandler();
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FineUploaderBasic.prototype = {
+ log: function(str, level) {
+ if (this._options.debug && (!level || level === 'info')) {
+ qq.log('[FineUploader ' + qq.version + '] ' + str);
+ }
+ else if (level && level !== 'info') {
+ qq.log('[FineUploader ' + qq.version + '] ' + str, level);
+
+ }
+ },
+ setParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.params = params;
+ }
+ else {
+ this._paramsStore.setParams(params, id);
+ }
+ },
+ setDeleteFileParams: function(params, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.params = params;
+ }
+ else {
+ this._deleteFileParamsStore.setParams(params, id);
+ }
+ },
+ setEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.request.endpoint = endpoint;
+ }
+ else {
+ this._endpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ getInProgress: function() {
+ return this._filesInProgress.length;
+ },
+ getNetUploads: function() {
+ return this._netUploaded;
+ },
+ uploadStoredFiles: function() {
+ var idToUpload;
+
+ if (this._storedIds.length === 0) {
+ this._itemError('noFilesError');
+ }
+ else {
+ while (this._storedIds.length) {
+ idToUpload = this._storedIds.shift();
+ this._filesInProgress.push(idToUpload);
+ this._handler.upload(idToUpload);
+ }
+ }
+ },
+ clearStoredFiles: function(){
+ this._storedIds = [];
+ },
+ retry: function(id) {
+ if (this._onBeforeManualRetry(id)) {
+ this._netUploadedOrQueued++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ cancel: function(id) {
+ this._handler.cancel(id);
+ },
+ cancelAll: function() {
+ var storedIdsCopy = [],
+ self = this;
+
+ qq.extend(storedIdsCopy, this._storedIds);
+ qq.each(storedIdsCopy, function(idx, storedFileId) {
+ self.cancel(storedFileId);
+ });
+
+ this._handler.cancelAll();
+ },
+ reset: function() {
+ this.log("Resetting uploader...");
+
+ this._handler.reset();
+ this._filesInProgress = [];
+ this._storedIds = [];
+ this._autoRetries = [];
+ this._retryTimeouts = [];
+ this._preventRetries = [];
+ this._button.reset();
+ this._paramsStore.reset();
+ this._endpointStore.reset();
+ this._netUploadedOrQueued = 0;
+ this._netUploaded = 0;
+ this._uploadData.reset();
+
+ if (this._pasteHandler) {
+ this._pasteHandler.reset();
+ }
+ },
+ addFiles: function(filesOrInputs, params, endpoint) {
+ var self = this,
+ verifiedFilesOrInputs = [],
+ fileOrInputIndex, fileOrInput, fileIndex;
+
+ if (filesOrInputs) {
+ if (!qq.isFileList(filesOrInputs)) {
+ filesOrInputs = [].concat(filesOrInputs);
+ }
+
+ for (fileOrInputIndex = 0; fileOrInputIndex < filesOrInputs.length; fileOrInputIndex+=1) {
+ fileOrInput = filesOrInputs[fileOrInputIndex];
+
+ if (qq.isFileOrInput(fileOrInput)) {
+ if (qq.isInput(fileOrInput) && qq.supportedFeatures.ajaxUploading) {
+ for (fileIndex = 0; fileIndex < fileOrInput.files.length; fileIndex++) {
+ verifiedFilesOrInputs.push(fileOrInput.files[fileIndex]);
+ }
+ }
+ else {
+ verifiedFilesOrInputs.push(fileOrInput);
+ }
+ }
+ else {
+ self.log(fileOrInput + ' is not a File or INPUT element! Ignoring!', 'warn');
+ }
+ }
+
+ this.log('Received ' + verifiedFilesOrInputs.length + ' files or inputs.');
+ this._prepareItemsForUpload(verifiedFilesOrInputs, params, endpoint);
+ }
+ },
+ addBlobs: function(blobDataOrArray, params, endpoint) {
+ if (blobDataOrArray) {
+ var blobDataArray = [].concat(blobDataOrArray),
+ verifiedBlobDataList = [],
+ self = this;
+
+ qq.each(blobDataArray, function(idx, blobData) {
+ if (qq.isBlob(blobData) && !qq.isFileOrInput(blobData)) {
+ verifiedBlobDataList.push({
+ blob: blobData,
+ name: self._options.blobs.defaultName
+ });
+ }
+ else if (qq.isObject(blobData) && blobData.blob && blobData.name) {
+ verifiedBlobDataList.push(blobData);
+ }
+ else {
+ self.log("addBlobs: entry at index " + idx + " is not a Blob or a BlobData object", "error");
+ }
+ });
+
+ this._prepareItemsForUpload(verifiedBlobDataList, params, endpoint);
+ }
+ else {
+ this.log("undefined or non-array parameter passed into addBlobs", "error");
+ }
+ },
+ getUuid: function(id) {
+ return this._handler.getUuid(id);
+ },
+ getResumableFilesData: function() {
+ return this._handler.getResumableFilesData();
+ },
+ getSize: function(id) {
+ return this._handler.getSize(id);
+ },
+ getName: function(id) {
+ return this._handler.getName(id);
+ },
+ setName: function(id, newName) {
+ this._handler.setName(id, newName);
+ this._uploadData.nameChanged(id, newName);
+ },
+ getFile: function(fileOrBlobId) {
+ return this._handler.getFile(fileOrBlobId);
+ },
+ deleteFile: function(id) {
+ this._onSubmitDelete(id);
+ },
+ setDeleteFileEndpoint: function(endpoint, id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id == null) {
+ this._options.deleteFile.endpoint = endpoint;
+ }
+ else {
+ this._deleteFileEndpointStore.setEndpoint(endpoint, id);
+ }
+ },
+ doesExist: function(fileOrBlobId) {
+ return this._handler.isValid(fileOrBlobId);
+ },
+ getUploads: function(optionalFilter) {
+ return this._uploadData.retrieve(optionalFilter);
+ },
+ _handleCheckedCallback: function(details) {
+ var self = this,
+ callbackRetVal = details.callback();
+
+ if (qq.isPromise(callbackRetVal)) {
+ this.log(details.name + " - waiting for " + details.name + " promise to be fulfilled for " + details.identifier);
+ return callbackRetVal.then(
+ function(successParam) {
+ self.log(details.name + " promise success for " + details.identifier);
+ details.onSuccess(successParam);
+ },
+ function() {
+ if (details.onFailure) {
+ self.log(details.name + " promise failure for " + details.identifier);
+ details.onFailure();
+ }
+ else {
+ self.log(details.name + " promise failure for " + details.identifier);
+ }
+ });
+ }
+
+ if (callbackRetVal !== false) {
+ details.onSuccess(callbackRetVal);
+ }
+ else {
+ if (details.onFailure) {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Invoking failure callback.")
+ details.onFailure();
+ }
+ else {
+ this.log(details.name + " - return value was 'false' for " + details.identifier + ". Will not proceed.")
+ }
+ }
+
+ return callbackRetVal;
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ var button = new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.supportedFeatures.ajaxUploading,
+ acceptFiles: this._options.validation.acceptFiles,
+ onChange: function(input){
+ self._onInputChange(input);
+ },
+ hoverClass: this._options.classes.buttonHover,
+ focusClass: this._options.classes.buttonFocus
+ });
+
+ this._disposeSupport.addDisposer(function() { button.dispose(); });
+ return button;
+ },
+ _createUploadHandler: function(){
+ var self = this;
+
+ return new qq.UploadHandler({
+ debug: this._options.debug,
+ forceMultipart: this._options.request.forceMultipart,
+ maxConnections: this._options.maxConnections,
+ customHeaders: this._options.request.customHeaders,
+ inputName: this._options.request.inputName,
+ uuidParamName: this._options.request.uuidName,
+ filenameParam: this._options.request.filenameParam,
+ totalFileSizeParamName: this._options.request.totalFileSizeName,
+ cors: this._options.cors,
+ demoMode: this._options.demoMode,
+ paramsInBody: this._options.request.paramsInBody,
+ paramsStore: this._paramsStore,
+ endpointStore: this._endpointStore,
+ chunking: this._options.chunking,
+ resume: this._options.resume,
+ blobs: this._options.blobs,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onProgress: function(id, name, loaded, total){
+ self._onProgress(id, name, loaded, total);
+ self._options.callbacks.onProgress(id, name, loaded, total);
+ },
+ onComplete: function(id, name, result, xhr){
+ self._onComplete(id, name, result, xhr);
+ self._options.callbacks.onComplete(id, name, result, xhr);
+ },
+ onCancel: function(id, name) {
+ return self._handleCheckedCallback({
+ name: "onCancel",
+ callback: qq.bind(self._options.callbacks.onCancel, self, id, name),
+ onSuccess: qq.bind(self._onCancel, self, id, name),
+ identifier: id
+ });
+ },
+ onUpload: function(id, name){
+ self._onUpload(id, name);
+ self._options.callbacks.onUpload(id, name);
+ },
+ onUploadChunk: function(id, name, chunkData){
+ self._options.callbacks.onUploadChunk(id, name, chunkData);
+ },
+ onResume: function(id, name, chunkData) {
+ return self._options.callbacks.onResume(id, name, chunkData);
+ },
+ onAutoRetry: function(id, name, responseJSON, xhr) {
+ self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
+
+ if (self._shouldAutoRetry(id, name, responseJSON)) {
+ self._maybeParseAndSendUploadError(id, name, responseJSON, xhr);
+ self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id] + 1);
+ self._onBeforeAutoRetry(id, name);
+
+ self._retryTimeouts[id] = setTimeout(function() {
+ self._onAutoRetry(id, name, responseJSON)
+ }, self._options.retry.autoAttemptDelay * 1000);
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ onUuidChanged: function(id, newUuid) {
+ self._uploadData.uuidChanged(id, newUuid);
+ }
+ });
+ },
+ _createDeleteHandler: function() {
+ var self = this;
+
+ return new qq.DeleteFileAjaxRequestor({
+ method: this._options.deleteFile.method,
+ maxConnections: this._options.maxConnections,
+ uuidParamName: this._options.request.uuidName,
+ customHeaders: this._options.deleteFile.customHeaders,
+ paramsStore: this._deleteFileParamsStore,
+ endpointStore: this._deleteFileEndpointStore,
+ demoMode: this._options.demoMode,
+ cors: this._options.cors,
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ onDelete: function(id) {
+ self._onDelete(id);
+ self._options.callbacks.onDelete(id);
+ },
+ onDeleteComplete: function(id, xhrOrXdr, isError) {
+ self._onDeleteComplete(id, xhrOrXdr, isError);
+ self._options.callbacks.onDeleteComplete(id, xhrOrXdr, isError);
+ }
+
+ });
+ },
+ _createPasteHandler: function() {
+ var self = this;
+
+ return new qq.PasteSupport({
+ targetElement: this._options.paste.targetElement,
+ callbacks: {
+ log: function(str, level) {
+ self.log(str, level);
+ },
+ pasteReceived: function(blob) {
+ self._handleCheckedCallback({
+ name: "onPasteReceived",
+ callback: qq.bind(self._options.callbacks.onPasteReceived, self, blob),
+ onSuccess: qq.bind(self._handlePasteSuccess, self, blob),
+ identifier: "pasted image"
+ });
+ }
+ }
+ });
+ },
+ _createUploadDataTracker: function() {
+ var self = this;
+
+ return new qq.UploadData({
+ getName: function(id) {
+ return self.getName(id);
+ },
+ getUuid: function(id) {
+ return self.getUuid(id);
+ },
+ getSize: function(id) {
+ return self.getSize(id);
+ },
+ onStatusChange: function(id, oldStatus, newStatus) {
+ self._onUploadStatusChange(id, oldStatus, newStatus);
+ self._options.callbacks.onStatusChange(id, oldStatus, newStatus);
+ }
+ });
+ },
+ _onUploadStatusChange: function(id, oldStatus, newStatus) {
+ //nothing to do in the basic uploader
+ },
+ _handlePasteSuccess: function(blob, extSuppliedName) {
+ var extension = blob.type.split("/")[1],
+ name = extSuppliedName;
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (name == null) {
+ name = this._options.paste.defaultName;
+ }
+
+ name += '.' + extension;
+
+ this.addBlobs({
+ name: name,
+ blob: blob
+ });
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ this._disposeSupport.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress.length){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, name) {
+ this._netUploadedOrQueued++;
+
+ if (this._options.autoUpload) {
+ this._filesInProgress.push(id);
+ }
+ },
+ _onProgress: function(id, name, loaded, total) {
+ //nothing to do yet in core uploader
+ },
+ _onComplete: function(id, name, result, xhr) {
+ if (!result.success) {
+ this._netUploadedOrQueued--;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_FAILED);
+ }
+ else {
+ this._netUploaded++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_SUCCESSFUL);
+ }
+
+ this._removeFromFilesInProgress(id);
+ this._maybeParseAndSendUploadError(id, name, result, xhr);
+ },
+ _onCancel: function(id, name) {
+ this._netUploadedOrQueued--;
+
+ this._removeFromFilesInProgress(id);
+
+ clearTimeout(this._retryTimeouts[id]);
+
+ var storedItemIndex = qq.indexOf(this._storedIds, id);
+ if (!this._options.autoUpload && storedItemIndex >= 0) {
+ this._storedIds.splice(storedItemIndex, 1);
+ }
+
+ this._uploadData.setStatus(id, qq.status.CANCELED);
+ },
+ _isDeletePossible: function() {
+ if (!this._options.deleteFile.enabled) {
+ return false;
+ }
+
+ if (this._options.cors.expected) {
+ if (qq.supportedFeatures.deleteFileCorsXhr) {
+ return true;
+ }
+
+ if (qq.supportedFeatures.deleteFileCorsXdr && this._options.cors.allowXdr) {
+ return true;
+ }
+
+ return false;
+ }
+
+ return true;
+ },
+ _onSubmitDelete: function(id, onSuccessCallback) {
+ if (this._isDeletePossible()) {
+ return this._handleCheckedCallback({
+ name: "onSubmitDelete",
+ callback: qq.bind(this._options.callbacks.onSubmitDelete, this, id),
+ onSuccess: onSuccessCallback || qq.bind(this._deleteHandler.sendDelete, this, id, this.getUuid(id)),
+ identifier: id
+ });
+ }
+ else {
+ this.log("Delete request ignored for ID " + id + ", delete feature is disabled or request not possible " +
+ "due to CORS on a user agent that does not support pre-flighting.", "warn");
+ return false;
+ }
+ },
+ _onDelete: function(id) {
+ this._uploadData.setStatus(id, qq.status.DELETING);
+ },
+ _onDeleteComplete: function(id, xhrOrXdr, isError) {
+ var name = this._handler.getName(id);
+
+ if (isError) {
+ this._uploadData.setStatus(id, qq.status.DELETE_FAILED);
+ this.log("Delete request for '" + name + "' has failed.", "error");
+
+ // For error reporing, we only have accesss to the response status if this is not
+ // an `XDomainRequest`.
+ if (xhrOrXdr.withCredentials === undefined) {
+ this._options.callbacks.onError(id, name, "Delete request failed", xhrOrXdr);
+ }
+ else {
+ this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhrOrXdr.status, xhrOrXdr);
+ }
+ }
+ else {
+ this._netUploadedOrQueued--;
+ this._netUploaded--;
+ this._handler.expunge(id);
+ this._uploadData.setStatus(id, qq.status.DELETED);
+ this.log("Delete request for '" + name + "' has succeeded.");
+ }
+ },
+ _removeFromFilesInProgress: function(id) {
+ var index = qq.indexOf(this._filesInProgress, id);
+ if (index >= 0) {
+ this._filesInProgress.splice(index, 1);
+ }
+ },
+ _onUpload: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.UPLOADING);
+ },
+ _onInputChange: function(input){
+ if (qq.supportedFeatures.ajaxUploading) {
+ this.addFiles(input.files);
+ }
+ else {
+ this.addFiles(input);
+ }
+
+ this._button.reset();
+ },
+ _onBeforeAutoRetry: function(id, name) {
+ this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + name + "...");
+ },
+ _onAutoRetry: function(id, name, responseJSON) {
+ this.log("Retrying " + name + "...");
+ this._autoRetries[id]++;
+ this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);
+ this._handler.retry(id);
+ },
+ _shouldAutoRetry: function(id, name, responseJSON) {
+ if (!this._preventRetries[id] && this._options.retry.enableAuto) {
+ if (this._autoRetries[id] === undefined) {
+ this._autoRetries[id] = 0;
+ }
+
+ return this._autoRetries[id] < this._options.retry.maxAutoAttempts;
+ }
+
+ return false;
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var itemLimit = this._options.validation.itemLimit;
+
+ if (this._preventRetries[id]) {
+ this.log("Retries are forbidden for id " + id, 'warn');
+ return false;
+ }
+ else if (this._handler.isValid(id)) {
+ var fileName = this._handler.getName(id);
+
+ if (this._options.callbacks.onManualRetry(id, fileName) === false) {
+ return false;
+ }
+
+ if (itemLimit > 0 && this._netUploadedOrQueued+1 > itemLimit) {
+ this._itemError("retryFailTooManyItems");
+ return false;
+ }
+
+ this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
+ this._filesInProgress.push(id);
+ return true;
+ }
+ else {
+ this.log("'" + id + "' is not a valid file ID", 'error');
+ return false;
+ }
+ },
+ _maybeParseAndSendUploadError: function(id, name, response, xhr) {
+ //assuming no one will actually set the response code to something other than 200 and still set 'success' to true
+ if (!response.success){
+ if (xhr && xhr.status !== 200 && !response.error) {
+ this._options.callbacks.onError(id, name, "XHR returned response code " + xhr.status, xhr);
+ }
+ else {
+ var errorReason = response.error ? response.error : this._options.text.defaultResponseError;
+ this._options.callbacks.onError(id, name, errorReason, xhr);
+ }
+ }
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ var validationDescriptors = this._getValidationDescriptors(items);
+
+ this._handleCheckedCallback({
+ name: "onValidateBatch",
+ callback: qq.bind(this._options.callbacks.onValidateBatch, this, validationDescriptors),
+ onSuccess: qq.bind(this._onValidateBatchCallbackSuccess, this, validationDescriptors, items, params, endpoint),
+ identifier: "batch validation"
+ });
+ },
+ _upload: function(blobOrFileContainer, params, endpoint) {
+ var id = this._handler.add(blobOrFileContainer),
+ name = this._handler.getName(id);
+
+ this._uploadData.added(id);
+
+ if (params) {
+ this.setParams(params, id);
+ }
+
+ if (endpoint) {
+ this.setEndpoint(endpoint, id);
+ }
+
+ this._handleCheckedCallback({
+ name: "onSubmit",
+ callback: qq.bind(this._options.callbacks.onSubmit, this, id, name),
+ onSuccess: qq.bind(this._onSubmitCallbackSuccess, this, id, name),
+ onFailure: qq.bind(this._fileOrBlobRejected, this, id, name),
+ identifier: id
+ });
+ },
+ _onSubmitCallbackSuccess: function(id, name) {
+ this._uploadData.setStatus(id, qq.status.SUBMITTED);
+
+ this._onSubmit.apply(this, arguments);
+ this._onSubmitted.apply(this, arguments);
+ this._options.callbacks.onSubmitted.apply(this, arguments);
+
+ if (this._options.autoUpload) {
+ if (!this._handler.upload(id)) {
+ this._uploadData.setStatus(id, qq.status.QUEUED);
+ }
+ }
+ else {
+ this._storeForLater(id);
+ }
+ },
+ _onSubmitted: function(id) {
+ //nothing to do in the base uploader
+ },
+ _storeForLater: function(id) {
+ this._storedIds.push(id);
+ },
+ _onValidateBatchCallbackSuccess: function(validationDescriptors, items, params, endpoint) {
+ var errorMessage,
+ itemLimit = this._options.validation.itemLimit,
+ proposedNetFilesUploadedOrQueued = this._netUploadedOrQueued + validationDescriptors.length;
+
+ if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {
+ if (items.length > 0) {
+ this._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(this._options.callbacks.onValidate, this, items[0]),
+ onSuccess: qq.bind(this._onValidateCallbackSuccess, this, items, 0, params, endpoint),
+ onFailure: qq.bind(this._onValidateCallbackFailure, this, items, 0, params, endpoint),
+ identifier: "Item '" + items[0].name + "', size: " + items[0].size
+ });
+ }
+ else {
+ this._itemError("noFilesError");
+ }
+ }
+ else {
+ errorMessage = this._options.messages.tooManyItemsError
+ .replace(/\{netItems\}/g, proposedNetFilesUploadedOrQueued)
+ .replace(/\{itemLimit\}/g, itemLimit);
+ this._batchError(errorMessage);
+ }
+ },
+ _onValidateCallbackSuccess: function(items, index, params, endpoint) {
+ var nextIndex = index+1,
+ validationDescriptor = this._getValidationDescriptor(items[index]),
+ validItem = false;
+
+ if (this._validateFileOrBlobData(items[index], validationDescriptor)) {
+ validItem = true;
+ this._upload(items[index], params, endpoint);
+ }
+
+ this._maybeProcessNextItemAfterOnValidateCallback(validItem, items, nextIndex, params, endpoint);
+ },
+ _onValidateCallbackFailure: function(items, index, params, endpoint) {
+ var nextIndex = index+ 1;
+
+ this._fileOrBlobRejected(undefined, items[0].name);
+
+ this._maybeProcessNextItemAfterOnValidateCallback(false, items, nextIndex, params, endpoint);
+ },
+ _maybeProcessNextItemAfterOnValidateCallback: function(validItem, items, index, params, endpoint) {
+ var self = this;
+
+ if (items.length > index) {
+ if (validItem || !this._options.validation.stopOnFirstInvalidFile) {
+ //use setTimeout to prevent a stack overflow with a large number of files in the batch & non-promissory callbacks
+ setTimeout(function() {
+ var validationDescriptor = self._getValidationDescriptor(items[index]);
+
+ self._handleCheckedCallback({
+ name: "onValidate",
+ callback: qq.bind(self._options.callbacks.onValidate, self, items[index]),
+ onSuccess: qq.bind(self._onValidateCallbackSuccess, self, items, index, params, endpoint),
+ onFailure: qq.bind(self._onValidateCallbackFailure, self, items, index, params, endpoint),
+ identifier: "Item '" + validationDescriptor.name + "', size: " + validationDescriptor.size
+ });
+ }, 0);
+ }
+ }
+ },
+ _validateFileOrBlobData: function(item, validationDescriptor) {
+ var name = validationDescriptor.name,
+ size = validationDescriptor.size,
+ valid = true;
+
+ if (this._options.callbacks.onValidate(validationDescriptor) === false) {
+ valid = false;
+ }
+
+ if (qq.isFileOrInput(item) && !this._isAllowedExtension(name)){
+ this._itemError('typeError', name);
+ valid = false;
+
+ }
+ else if (size === 0){
+ this._itemError('emptyError', name);
+ valid = false;
+
+ }
+ else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
+ this._itemError('sizeError', name);
+ valid = false;
+
+ }
+ else if (size && size < this._options.validation.minSizeLimit){
+ this._itemError('minSizeError', name);
+ valid = false;
+ }
+
+ if (!valid) {
+ this._fileOrBlobRejected(undefined, name);
+ }
+
+ return valid;
+ },
+ _fileOrBlobRejected: function(id, name) {
+ if (id !== undefined) {
+ this._uploadData.setStatus(id, qq.status.REJECTED);
+ }
+ },
+ _itemError: function(code, maybeNameOrNames) {
+ var message = this._options.messages[code],
+ allowedExtensions = [],
+ names = [].concat(maybeNameOrNames),
+ name = names[0],
+ extensionsForMessage, placeholderMatch;
+
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ qq.each(this._options.validation.allowedExtensions, function(idx, allowedExtension) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExtension)) {
+ allowedExtensions.push(allowedExtension);
+ }
+ });
+
+ extensionsForMessage = allowedExtensions.join(', ').toLowerCase();
+
+ r('{file}', this._options.formatFileName(name));
+ r('{extensions}', extensionsForMessage);
+ r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
+
+ placeholderMatch = message.match(/(\{\w+\})/g);
+ if (placeholderMatch !== null) {
+ qq.each(placeholderMatch, function(idx, placeholder) {
+ r(placeholder, names[idx]);
+ });
+ }
+
+ this._options.callbacks.onError(null, name, message, undefined);
+
+ return message;
+ },
+ _batchError: function(message) {
+ this._options.callbacks.onError(null, null, message, undefined);
+ },
+ _isAllowedExtension: function(fileName){
+ var allowed = this._options.validation.allowedExtensions,
+ valid = false;
+
+ if (!allowed.length) {
+ return true;
+ }
+
+ qq.each(allowed, function(idx, allowedExt) {
+ /**
+ * If an argument is not a string, ignore it. Added when a possible issue with MooTools hijacking the
+ * `allowedExtensions` array was discovered. See case #735 in the issue tracker for more details.
+ */
+ if (qq.isString(allowedExt)) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var extRegex = new RegExp('\\.' + allowedExt + "$", 'i');
+
+ if (fileName.match(extRegex) != null) {
+ valid = true;
+ return false;
+ }
+ }
+ });
+
+ return valid;
+ },
+ _formatSize: function(bytes){
+ var i = -1;
+ do {
+ bytes = bytes / 1000;
+ i++;
+ } while (bytes > 999);
+
+ return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];
+ },
+ _wrapCallbacks: function() {
+ var self, safeCallback;
+
+ self = this;
+
+ safeCallback = function(name, callback, args) {
+ try {
+ return callback.apply(self, args);
+ }
+ catch (exception) {
+ self.log("Caught exception in '" + name + "' callback - " + exception.message, 'error');
+ }
+ };
+
+ for (var prop in this._options.callbacks) {
+ (function() {
+ var callbackName, callbackFunc;
+ callbackName = prop;
+ callbackFunc = self._options.callbacks[callbackName];
+ self._options.callbacks[callbackName] = function() {
+ return safeCallback(callbackName, callbackFunc, arguments);
+ };
+ }());
+ }
+ },
+ _parseFileOrBlobDataName: function(fileOrBlobData) {
+ var name;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (fileOrBlobData.value) {
+ // it is a file input
+ // get input value and remove path to normalize
+ name = fileOrBlobData.value.replace(/.*(\/|\\)/, "");
+ } else {
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ name = (fileOrBlobData.fileName !== null && fileOrBlobData.fileName !== undefined) ? fileOrBlobData.fileName : fileOrBlobData.name;
+ }
+ }
+ else {
+ name = fileOrBlobData.name;
+ }
+
+ return name;
+ },
+ _parseFileOrBlobDataSize: function(fileOrBlobData) {
+ var size;
+
+ if (qq.isFileOrInput(fileOrBlobData)) {
+ if (!fileOrBlobData.value){
+ // fix missing properties in Safari 4 and firefox 11.0a2
+ size = (fileOrBlobData.fileSize !== null && fileOrBlobData.fileSize !== undefined) ? fileOrBlobData.fileSize : fileOrBlobData.size;
+ }
+ }
+ else {
+ size = fileOrBlobData.blob.size;
+ }
+
+ return size;
+ },
+ _getValidationDescriptor: function(fileOrBlobData) {
+ var name, size, fileDescriptor;
+
+ fileDescriptor = {};
+ name = this._parseFileOrBlobDataName(fileOrBlobData);
+ size = this._parseFileOrBlobDataSize(fileOrBlobData);
+
+ fileDescriptor.name = name;
+ if (size !== undefined) {
+ fileDescriptor.size = size;
+ }
+
+ return fileDescriptor;
+ },
+ _getValidationDescriptors: function(files) {
+ var self = this,
+ fileDescriptors = [];
+
+ qq.each(files, function(idx, file) {
+ fileDescriptors.push(self._getValidationDescriptor(file));
+ });
+
+ return fileDescriptors;
+ },
+ _createParamsStore: function(type) {
+ var paramsStore = {},
+ self = this;
+
+ return {
+ setParams: function(params, id) {
+ var paramsCopy = {};
+ qq.extend(paramsCopy, params);
+ paramsStore[id] = paramsCopy;
+ },
+
+ getParams: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ var paramsCopy = {};
+
+ if (id != null && paramsStore[id]) {
+ qq.extend(paramsCopy, paramsStore[id]);
+ }
+ else {
+ qq.extend(paramsCopy, self._options[type].params);
+ }
+
+ return paramsCopy;
+ },
+
+ remove: function(fileId) {
+ return delete paramsStore[fileId];
+ },
+
+ reset: function() {
+ paramsStore = {};
+ }
+ };
+ },
+ _createEndpointStore: function(type) {
+ var endpointStore = {},
+ self = this;
+
+ return {
+ setEndpoint: function(endpoint, id) {
+ endpointStore[id] = endpoint;
+ },
+
+ getEndpoint: function(id) {
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (id != null && endpointStore[id]) {
+ return endpointStore[id];
+ }
+
+ return self._options[type].endpoint;
+ },
+
+ remove: function(fileId) {
+ return delete endpointStore[fileId];
+ },
+
+ reset: function() {
+ endpointStore = {};
+ }
+ };
+ },
+ _handleCameraAccess: function() {
+ if (this._options.camera.ios && qq.ios()) {
+ this._options.multiple = false;
+
+ if (this._options.validation.acceptFiles === null) {
+ this._options.validation.acceptFiles = "image/*;capture=camera";
+ }
+ else {
+ this._options.validation.acceptFiles += ",image/*;capture=camera";
+ }
+ }
+ }
+};
+;/*globals qq, document*/
+qq.DragAndDrop = function(o) {
+ "use strict";
+
+ var options, dz,
+ droppedFiles = [],
+ disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ dropZoneElements: [],
+ hideDropZonesBeforeEnter: false,
+ allowMultipleItems: true,
+ classes: {
+ dropActive: null
+ },
+ callbacks: new qq.DragAndDrop.callbacks()
+ };
+
+ qq.extend(options, o, true);
+
+ setupDragDrop();
+
+ function uploadDroppedFiles(files) {
+ options.callbacks.dropLog('Grabbed ' + files.length + " dropped files.");
+ dz.dropDisabled(false);
+ options.callbacks.processingDroppedFilesComplete(files);
+ }
+
+ function traverseFileTree(entry) {
+ var dirReader, i,
+ parseEntryPromise = new qq.Promise();
+
+ if (entry.isFile) {
+ entry.file(function(file) {
+ droppedFiles.push(file);
+ parseEntryPromise.success();
+ },
+ function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+ else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ dirReader.readEntries(function(entries) {
+ var entriesLeft = entries.length;
+
+ for (i = 0; i < entries.length; i+=1) {
+ traverseFileTree(entries[i]).done(function() {
+ entriesLeft-=1;
+
+ if (entriesLeft === 0) {
+ parseEntryPromise.success();
+ }
+ });
+ }
+
+ if (!entries.length) {
+ parseEntryPromise.success();
+ }
+ }, function(fileError) {
+ options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'. FileError code " + fileError.code + ".", "error");
+ parseEntryPromise.failure();
+ });
+ }
+
+ return parseEntryPromise;
+ }
+
+ function handleDataTransfer(dataTransfer) {
+ var i, items, entry,
+ pendingFolderPromises = [],
+ handleDataTransferPromise = new qq.Promise();
+
+ options.callbacks.processingDroppedFiles();
+ dz.dropDisabled(true);
+
+ if (dataTransfer.files.length > 1 && !options.allowMultipleItems) {
+ options.callbacks.processingDroppedFilesComplete([]);
+ options.callbacks.dropError('tooManyFilesError', "");
+ dz.dropDisabled(false);
+ handleDataTransferPromise.failure();
+ }
+ else {
+ droppedFiles = [];
+
+ if (qq.isFolderDropSupported(dataTransfer)) {
+ items = dataTransfer.items;
+
+ for (i = 0; i < items.length; i+=1) {
+ entry = items[i].webkitGetAsEntry();
+ if (entry) {
+ //due to a bug in Chrome's File System API impl - #149735
+ if (entry.isFile) {
+ droppedFiles.push(items[i].getAsFile());
+ }
+
+ else {
+ pendingFolderPromises.push(traverseFileTree(entry).done(function() {
+ pendingFolderPromises.pop();
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }));
+ }
+ }
+ }
+ }
+ else {
+ droppedFiles = dataTransfer.files;
+ }
+
+ if (pendingFolderPromises.length === 0) {
+ handleDataTransferPromise.success();
+ }
+ }
+
+ return handleDataTransferPromise;
+ }
+
+ function setupDropzone(dropArea){
+ dz = new qq.UploadDropZone({
+ element: dropArea,
+ onEnter: function(e){
+ qq(dropArea).addClass(options.classes.dropActive);
+ e.stopPropagation();
+ },
+ onLeaveNotDescendants: function(e){
+ qq(dropArea).removeClass(options.classes.dropActive);
+ },
+ onDrop: function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ qq(dropArea).removeClass(options.classes.dropActive);
+
+ handleDataTransfer(e.dataTransfer).done(function() {
+ uploadDroppedFiles(droppedFiles);
+ });
+ }
+ });
+
+ disposeSupport.addDisposer(function() {
+ dz.dispose();
+ });
+
+ if (options.hideDropZonesBeforeEnter) {
+ qq(dropArea).hide();
+ }
+ }
+
+ function isFileDrag(dragEvent) {
+ var fileDrag;
+
+ qq.each(dragEvent.dataTransfer.types, function(key, val) {
+ if (val === 'Files') {
+ fileDrag = true;
+ return false;
+ }
+ });
+
+ return fileDrag;
+ }
+
+ function setupDragDrop(){
+ var dropZones = options.dropZoneElements;
+
+ qq.each(dropZones, function(idx, dropZone) {
+ setupDropzone(dropZone);
+ })
+
+ // IE <= 9 does not support the File API used for drag+drop uploads
+ if (dropZones.length && (!qq.ie() || qq.ie10())) {
+ disposeSupport.attach(document, 'dragenter', function(e) {
+ if (!dz.dropDisabled() && isFileDrag(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).css({display: 'block'});
+ });
+ }
+ });
+ }
+ disposeSupport.attach(document, 'dragleave', function(e){
+ if (options.hideDropZonesBeforeEnter && qq.FineUploader.prototype._leaving_document_out(e)) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ });
+ disposeSupport.attach(document, 'drop', function(e){
+ if (options.hideDropZonesBeforeEnter) {
+ qq.each(dropZones, function(idx, dropZone) {
+ qq(dropZone).hide();
+ });
+ }
+ e.preventDefault();
+ });
+ }
+
+ return {
+ setupExtraDropzone: function(element) {
+ options.dropZoneElements.push(element);
+ setupDropzone(element);
+ },
+
+ removeDropzone: function(element) {
+ var i,
+ dzs = options.dropZoneElements;
+
+ for(i in dzs) {
+ if (dzs[i] === element) {
+ return dzs.splice(i, 1);
+ }
+ }
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ dz.dispose();
+ }
+ };
+};
+
+qq.DragAndDrop.callbacks = function() {
+ return {
+ processingDroppedFiles: function() {},
+ processingDroppedFilesComplete: function(files) {},
+ dropError: function(code, errorSpecifics) {
+ qq.log("Drag & drop error code '" + code + " with these specifics: '" + errorSpecifics + "'", "error");
+ },
+ dropLog: function(message, level) {
+ qq.log(message, level);
+ }
+ }
+}
+
+qq.UploadDropZone = function(o){
+ "use strict";
+
+ var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();
+
+ options = {
+ element: null,
+ onEnter: function(e){},
+ onLeave: function(e){},
+ // is not fired when leaving element by hovering descendants
+ onLeaveNotDescendants: function(e){},
+ onDrop: function(e){}
+ };
+
+ qq.extend(options, o);
+ element = options.element;
+
+ function dragover_should_be_canceled(){
+ return qq.safari() || (qq.firefox() && qq.windows());
+ }
+
+ function disableDropOutside(e){
+ // run only once for all instances
+ if (!dropOutsideDisabled ){
+
+ // for these cases we need to catch onDrop to reset dropArea
+ if (dragover_should_be_canceled){
+ disposeSupport.attach(document, 'dragover', function(e){
+ e.preventDefault();
+ });
+ } else {
+ disposeSupport.attach(document, 'dragover', function(e){
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
+ });
+ }
+
+ dropOutsideDisabled = true;
+ }
+ }
+
+ function isValidFileDrag(e){
+ // e.dataTransfer currently causing IE errors
+ // IE9 does NOT support file API, so drag-and-drop is not possible
+ if (qq.ie() && !qq.ie10()) {
+ return false;
+ }
+
+ var effectTest, dt = e.dataTransfer,
+ // do not check dt.types.contains in webkit, because it crashes safari 4
+ isSafari = qq.safari();
+
+ // dt.effectAllowed is none in Safari 5
+ // dt.types.contains check is for firefox
+ effectTest = qq.ie10() ? true : dt.effectAllowed !== 'none';
+ return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
+ }
+
+ function isOrSetDropDisabled(isDisabled) {
+ if (isDisabled !== undefined) {
+ preventDrop = isDisabled;
+ }
+ return preventDrop;
+ }
+
+ function attachEvents(){
+ disposeSupport.attach(element, 'dragover', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
+ if (effect === 'move' || effect === 'linkMove'){
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
+ } else {
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ disposeSupport.attach(element, 'dragenter', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+ options.onEnter(e);
+ }
+ });
+
+ disposeSupport.attach(element, 'dragleave', function(e){
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ options.onLeave(e);
+
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+ // do not fire when moving a mouse over a descendant
+ if (qq(this).contains(relatedTarget)) {
+ return;
+ }
+
+ options.onLeaveNotDescendants(e);
+ });
+
+ disposeSupport.attach(element, 'drop', function(e){
+ if (!isOrSetDropDisabled()) {
+ if (!isValidFileDrag(e)) {
+ return;
+ }
+
+ e.preventDefault();
+ options.onDrop(e);
+ }
+ });
+ }
+
+ disableDropOutside();
+ attachEvents();
+
+ return {
+ dropDisabled: function(isDisabled) {
+ return isOrSetDropDisabled(isDisabled);
+ },
+
+ dispose: function() {
+ disposeSupport.dispose();
+ }
+ };
+};
+;/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FineUploaderBasic
+ */
+qq.FineUploader = function(o){
+ // call parent constructor
+ qq.FineUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
+ element: null,
+ listElement: null,
+ dragAndDrop: {
+ extraDropzones: [],
+ hideDropzones: true,
+ disableDefaultDropzone: false
+ },
+ text: {
+ uploadButton: 'Upload a file',
+ cancelButton: 'Cancel',
+ retryButton: 'Retry',
+ deleteButton: 'Delete',
+ failUpload: 'Upload failed',
+ dragZone: 'Drop files here to upload',
+ dropProcessing: 'Processing dropped files...',
+ formatProgress: "{percent}% of {total_size}",
+ waitingForResponse: "Processing..."
+ },
+ template: '' +
+ ((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
+ (!this._options.button ? '
' : '') +
+ '
{dropProcessingText} ' +
+ (!this._options.listElement ? '
' : '') +
+ '
',
+
+ // template for one item in file list
+ fileTemplate: '' +
+ '
' +
+ ' ' +
+ ' ' +
+ (this._options.editFilename && this._options.editFilename.enabled ? ' ' : '') +
+ ' ' +
+ (this._options.editFilename && this._options.editFilename.enabled ? ' ' : '') +
+ ' ' +
+ '{cancelButtonText} ' +
+ '{retryButtonText} ' +
+ '{deleteButtonText} ' +
+ '{statusText} ' +
+ ' ',
+ classes: {
+ button: 'qq-upload-button',
+ drop: 'qq-upload-drop-area',
+ dropActive: 'qq-upload-drop-area-active',
+ list: 'qq-upload-list',
+ progressBar: 'qq-progress-bar',
+ file: 'qq-upload-file',
+ spinner: 'qq-upload-spinner',
+ finished: 'qq-upload-finished',
+ retrying: 'qq-upload-retrying',
+ retryable: 'qq-upload-retryable',
+ size: 'qq-upload-size',
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry',
+ statusText: 'qq-upload-status-text',
+ editFilenameInput: 'qq-edit-filename',
+
+ success: 'qq-upload-success',
+ fail: 'qq-upload-fail',
+
+ successIcon: null,
+ failIcon: null,
+ editNameIcon: 'qq-edit-filename-icon',
+ editable: 'qq-editable',
+
+ dropProcessing: 'qq-drop-processing',
+ dropProcessingSpinner: 'qq-drop-processing-spinner'
+ },
+ failedUploadTextDisplay: {
+ mode: 'default', //default, custom, or none
+ maxChars: 50,
+ responseProperty: 'error',
+ enableTooltip: true
+ },
+ messages: {
+ tooManyFilesError: "You may only drop one file",
+ unsupportedBrowser: "Unrecoverable error - this browser does not permit file uploading of any kind."
+ },
+ retry: {
+ showAutoRetryNote: true,
+ autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
+ showButton: false
+ },
+ deleteFile: {
+ forceConfirm: false,
+ confirmMessage: "Are you sure you want to delete {filename}?",
+ deletingStatusText: "Deleting...",
+ deletingFailedText: "Delete failed"
+
+ },
+ display: {
+ fileSizeOnSubmit: false,
+ prependFiles: false
+ },
+ paste: {
+ promptForName: false,
+ namePromptMessage: "Please name this image"
+ },
+ editFilename: {
+ enabled: false
+ },
+ showMessage: function(message){
+ setTimeout(function() {
+ window.alert(message);
+ }, 0);
+ },
+ showConfirm: function(message, okCallback, cancelCallback) {
+ setTimeout(function() {
+ var result = window.confirm(message);
+ if (result) {
+ okCallback();
+ }
+ else if (cancelCallback) {
+ cancelCallback();
+ }
+ }, 0);
+ },
+ showPrompt: function(message, defaultValue) {
+ var promise = new qq.Promise(),
+ retVal = window.prompt(message, defaultValue);
+
+ /*jshint eqeqeq: true, eqnull: true*/
+ if (retVal != null && qq.trimStr(retVal).length > 0) {
+ promise.success(retVal);
+ }
+ else {
+ promise.failure("Undefined or invalid user-supplied value.");
+ }
+
+ return promise;
+ }
+ }, true);
+
+ // overwrite options with user supplied
+ qq.extend(this._options, o, true);
+
+ if (!qq.supportedFeatures.uploading || (this._options.cors.expected && !qq.supportedFeatures.uploadCors)) {
+ this._options.element.innerHTML = "" + this._options.messages.unsupportedBrowser + "
"
+ }
+ else {
+ this._wrapCallbacks();
+
+ // overwrite the upload button text if any
+ // same for the Cancel button and Fail message text
+ this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
+ this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
+
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+
+ this._classes = this._options.classes;
+
+ if (!this._button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._deleteRetryOrCancelClickHandler = this._bindDeleteRetryOrCancelClickEvent();
+
+ // A better approach would be to check specifically for focusin event support by querying the DOM API,
+ // but the DOMFocusIn event is not exposed as a property, so we have to resort to UA string sniffing.
+ this._focusinEventSupported = !qq.firefox();
+
+ if (this._isEditFilenameEnabled()) {
+ this._filenameClickHandler = this._bindFilenameClickEvent();
+ this._filenameInputFocusInHandler = this._bindFilenameInputFocusInEvent();
+ this._filenameInputFocusHandler = this._bindFilenameInputFocusEvent();
+ }
+
+ this._dnd = this._setupDragAndDrop();
+
+ if (this._options.paste.targetElement && this._options.paste.promptForName) {
+ this._setupPastePrompt();
+ }
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ }
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
+
+qq.extend(qq.FineUploader.prototype, {
+ clearStoredFiles: function() {
+ qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
+ this._listElement.innerHTML = "";
+ },
+ addExtraDropzone: function(element){
+ this._dnd.setupExtraDropzone(element);
+ },
+ removeExtraDropzone: function(element){
+ return this._dnd.removeDropzone(element);
+ },
+ getItemByFileId: function(id){
+ var item = this._listElement.firstChild;
+
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
+ while (item){
+ if (item.qqFileId == id) return item;
+ item = item.nextSibling;
+ }
+ },
+ reset: function() {
+ qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
+ if (!this._options.button) {
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
+ }
+
+ this._dnd.dispose();
+ this._dnd = this._setupDragAndDrop();
+
+ this._totalFilesInBatch = 0;
+ this._filesInBatchAddedToUi = 0;
+ },
+ _removeFileItem: function(fileId) {
+ var item = this.getItemByFileId(fileId);
+ qq(item).remove();
+ },
+ _setupDragAndDrop: function() {
+ var self = this,
+ dropProcessingEl = this._find(this._element, 'dropProcessing'),
+ dropZoneElements = this._options.dragAndDrop.extraDropzones,
+ preventSelectFiles;
+
+ preventSelectFiles = function(event) {
+ event.preventDefault();
+ };
+
+ if (!this._options.dragAndDrop.disableDefaultDropzone) {
+ dropZoneElements.push(this._find(this._options.element, 'drop'));
+ }
+
+ return new qq.DragAndDrop({
+ dropZoneElements: dropZoneElements,
+ hideDropZonesBeforeEnter: this._options.dragAndDrop.hideDropzones,
+ allowMultipleItems: this._options.multiple,
+ classes: {
+ dropActive: this._options.classes.dropActive
+ },
+ callbacks: {
+ processingDroppedFiles: function() {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).css({display: 'block'});
+ qq(input).attach('click', preventSelectFiles);
+ },
+ processingDroppedFilesComplete: function(files) {
+ var input = self._button.getInput();
+
+ qq(dropProcessingEl).hide();
+ qq(input).detach('click', preventSelectFiles);
+
+ if (files) {
+ self.addFiles(files);
+ }
+ },
+ dropError: function(code, errorData) {
+ self._itemError(code, errorData);
+ },
+ dropLog: function(message, level) {
+ self.log(message, level);
+ }
+ }
+ });
+ },
+ _bindDeleteRetryOrCancelClickEvent: function() {
+ var self = this;
+
+ return new qq.DeleteRetryOrCancelClickHandler({
+ listElement: this._listElement,
+ classes: this._classes,
+ log: function(message, lvl) {
+ self.log(message, lvl);
+ },
+ onDeleteFile: function(fileId) {
+ self.deleteFile(fileId);
+ },
+ onCancel: function(fileId) {
+ self.cancel(fileId);
+ },
+ onRetry: function(fileId) {
+ var item = self.getItemByFileId(fileId);
+
+ qq(item).removeClass(self._classes.retryable);
+ self.retry(fileId);
+ },
+ onGetName: function(fileId) {
+ return self.getName(fileId);
+ }
+ });
+ },
+ _isEditFilenameEnabled: function() {
+ return this._options.editFilename.enabled && !this._options.autoUpload;
+ },
+ _filenameEditHandler: function() {
+ var self = this;
+
+ return {
+ listElement: this._listElement,
+ classes: this._classes,
+ log: function(message, lvl) {
+ self.log(message, lvl);
+ },
+ onGetUploadStatus: function(fileId) {
+ return self.getUploads({id: fileId}).status;
+ },
+ onGetName: function(fileId) {
+ return self.getName(fileId);
+ },
+ onSetName: function(fileId, newName) {
+ var item = self.getItemByFileId(fileId),
+ qqFilenameDisplay = qq(self._find(item, 'file')),
+ formattedFilename = self._options.formatFileName(newName);
+
+ qqFilenameDisplay.setText(formattedFilename);
+ self.setName(fileId, newName);
+ },
+ onGetInput: function(item) {
+ return self._find(item, 'editFilenameInput');
+ },
+ onEditingStatusChange: function(fileId, isEditing) {
+ var item = self.getItemByFileId(fileId),
+ qqInput = qq(self._find(item, 'editFilenameInput')),
+ qqFilenameDisplay = qq(self._find(item, 'file')),
+ qqEditFilenameIcon = qq(self._find(item, 'editNameIcon')),
+ editableClass = self._classes.editable;
+
+ if (isEditing) {
+ qqInput.addClass('qq-editing');
+
+ qqFilenameDisplay.hide();
+ qqEditFilenameIcon.removeClass(editableClass);
+ }
+ else {
+ qqInput.removeClass('qq-editing');
+ qqFilenameDisplay.css({display: ''});
+ qqEditFilenameIcon.addClass(editableClass);
+ }
+
+ // Force IE8 and older to repaint
+ qq(item).addClass('qq-temp').removeClass('qq-temp');
+ }
+ };
+ },
+ _onUploadStatusChange: function(id, oldStatus, newStatus) {
+ if (this._isEditFilenameEnabled()) {
+ var item = this.getItemByFileId(id),
+ editableClass = this._classes.editable,
+ qqFilenameDisplay, qqEditFilenameIcon;
+
+ // Status for a file exists before it has been added to the DOM, so we must be careful here.
+ if (item && newStatus !== qq.status.SUBMITTED) {
+ qqFilenameDisplay = qq(this._find(item, 'file'));
+ qqEditFilenameIcon = qq(this._find(item, 'editNameIcon'));
+
+ qqFilenameDisplay.removeClass(editableClass);
+ qqEditFilenameIcon.removeClass(editableClass);
+ }
+ }
+ },
+ _bindFilenameInputFocusInEvent: function() {
+ var spec = qq.extend({}, this._filenameEditHandler());
+
+ return new qq.FilenameInputFocusInHandler(spec);
+ },
+ _bindFilenameInputFocusEvent: function() {
+ var spec = qq.extend({}, this._filenameEditHandler());
+
+ return new qq.FilenameInputFocusHandler(spec);
+ },
+ _bindFilenameClickEvent: function() {
+ var spec = qq.extend({}, this._filenameEditHandler());
+
+ return new qq.FilenameClickHandler(spec);
+ },
+ _leaving_document_out: function(e){
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
+ },
+ _storeForLater: function(id) {
+ qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
+ var item = this.getItemByFileId(id);
+ qq(this._find(item, 'spinner')).hide();
+ },
+ /**
+ * Gets one of the elements listed in this._options.classes
+ **/
+ _find: function(parent, type) {
+ var element = qq(parent).getByClass(this._options.classes[type])[0];
+ if (!element){
+ throw new Error('element not found ' + type);
+ }
+
+ return element;
+ },
+ _onSubmit: function(id, name) {
+ qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
+ this._addToList(id, name);
+ },
+ // The file item has been added to the DOM.
+ _onSubmitted: function(id) {
+ // If the edit filename feature is enabled, mark the filename element as "editable" and the associated edit icon
+ if (this._isEditFilenameEnabled()) {
+ var item = this.getItemByFileId(id),
+ qqFilenameDisplay = qq(this._find(item, 'file')),
+ qqEditFilenameIcon = qq(this._find(item, 'editNameIcon')),
+ editableClass = this._classes.editable;
+
+ qqFilenameDisplay.addClass(editableClass);
+ qqEditFilenameIcon.addClass(editableClass);
+
+ // If the focusin event is not supported, we must add a focus handler to the newly create edit filename text input
+ if (!this._focusinEventSupported) {
+ this._filenameInputFocusHandler.addHandler(this._find(item, 'editFilenameInput'));
+ }
+ }
+ },
+ // Update the progress bar & percentage as the file is uploaded
+ _onProgress: function(id, name, loaded, total){
+ qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+ var item, progressBar, percent, cancelLink;
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+ percent = Math.round(loaded / total * 100);
+
+ if (loaded === total) {
+ cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).hide();
+
+ qq(progressBar).hide();
+ qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
+
+ // If last byte was sent, display total file size
+ this._displayFileSize(id);
+ }
+ else {
+ // If still uploading, display percentage - total size is actually the total request(s) size
+ this._displayFileSize(id, loaded, total);
+
+ qq(progressBar).css({display: 'block'});
+ }
+
+ // Update progress bar element
+ qq(progressBar).css({width: percent + '%'});
+ },
+ _onComplete: function(id, name, result, xhr){
+ qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id);
+
+ qq(this._find(item, 'statusText')).clearText();
+
+ qq(item).removeClass(this._classes.retrying);
+ qq(this._find(item, 'progressBar')).hide();
+
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ qq(this._find(item, 'cancel')).hide();
+ }
+ qq(this._find(item, 'spinner')).hide();
+
+ if (result.success) {
+ if (this._isDeletePossible()) {
+ this._showDeleteLink(id);
+ }
+
+ qq(item).addClass(this._classes.success);
+ if (this._classes.successIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.successIcon);
+ }
+ } else {
+ qq(item).addClass(this._classes.fail);
+ if (this._classes.failIcon) {
+ this._find(item, 'finished').style.display = "inline-block";
+ qq(item).addClass(this._classes.failIcon);
+ }
+ if (this._options.retry.showButton && !this._preventRetries[id]) {
+ qq(item).addClass(this._classes.retryable);
+ }
+ this._controlFailureTextDisplay(item, result);
+ }
+ },
+ _onUpload: function(id, name){
+ qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
+
+ this._showSpinner(id);
+ },
+ _onCancel: function(id, name) {
+ qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
+ this._removeFileItem(id);
+ },
+ _onBeforeAutoRetry: function(id) {
+ var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
+
+ qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
+
+ item = this.getItemByFileId(id);
+ progressBar = this._find(item, 'progressBar');
+
+ this._showCancelLink(item);
+ progressBar.style.width = 0;
+ qq(progressBar).hide();
+
+ if (this._options.retry.showAutoRetryNote) {
+ failTextEl = this._find(item, 'statusText');
+ retryNumForDisplay = this._autoRetries[id] + 1;
+ maxAuto = this._options.retry.maxAutoAttempts;
+
+ retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
+ retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
+
+ qq(failTextEl).setText(retryNote);
+ if (retryNumForDisplay === 1) {
+ qq(item).addClass(this._classes.retrying);
+ }
+ }
+ },
+ //return false if we should not attempt the requested retry
+ _onBeforeManualRetry: function(id) {
+ var item = this.getItemByFileId(id);
+
+ if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
+ this._find(item, 'progressBar').style.width = 0;
+ qq(item).removeClass(this._classes.fail);
+ qq(this._find(item, 'statusText')).clearText();
+ this._showSpinner(id);
+ this._showCancelLink(item);
+ return true;
+ }
+ else {
+ qq(item).addClass(this._classes.retryable);
+ return false;
+ }
+ },
+ _onSubmitDelete: function(id) {
+ var onSuccessCallback = qq.bind(this._onSubmitDeleteSuccess, this, id);
+
+ qq.FineUploaderBasic.prototype._onSubmitDelete.call(this, id, onSuccessCallback);
+ },
+ _onSubmitDeleteSuccess: function(id) {
+ if (this._options.deleteFile.forceConfirm) {
+ this._showDeleteConfirm(id);
+ }
+ else {
+ this._sendDeleteRequest(id);
+ }
+ },
+ _onDeleteComplete: function(id, xhr, isError) {
+ qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
+
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(spinnerEl).hide();
+
+ if (isError) {
+ qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
+ this._showDeleteLink(id);
+ }
+ else {
+ this._removeFileItem(id);
+ }
+ },
+ _sendDeleteRequest: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton'),
+ statusTextEl = this._find(item, 'statusText');
+
+ qq(deleteLink).hide();
+ this._showSpinner(id);
+ qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
+ this._deleteHandler.sendDelete(id, this.getUuid(id));
+ },
+ _showDeleteConfirm: function(id) {
+ var fileName = this._handler.getName(id),
+ confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
+ uuid = this.getUuid(id),
+ self = this;
+
+ this._options.showConfirm(confirmMessage, function() {
+ self._sendDeleteRequest(id);
+ });
+ },
+ _addToList: function(id, name){
+ var item = qq.toElement(this._options.fileTemplate);
+ if (this._options.disableCancelForFormUploads && !qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+ qq(cancelLink).remove();
+ }
+
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq(fileElement).setText(this._options.formatFileName(name));
+ qq(this._find(item, 'size')).hide();
+ if (!this._options.multiple) {
+ this._handler.cancelAll();
+ this._clearList();
+ }
+
+ if (this._options.display.prependFiles) {
+ this._prependItem(item);
+ }
+ else {
+ this._listElement.appendChild(item);
+ }
+ this._filesInBatchAddedToUi += 1;
+
+ if (this._options.display.fileSizeOnSubmit && qq.supportedFeatures.ajaxUploading) {
+ this._displayFileSize(id);
+ }
+ },
+ _prependItem: function(item) {
+ var parentEl = this._listElement,
+ beforeEl = parentEl.firstChild;
+
+ if (this._totalFilesInBatch > 1 && this._filesInBatchAddedToUi > 0) {
+ beforeEl = qq(parentEl).children()[this._filesInBatchAddedToUi - 1].nextSibling;
+
+ }
+
+ parentEl.insertBefore(item, beforeEl);
+ },
+ _clearList: function(){
+ this._listElement.innerHTML = '';
+ this.clearStoredFiles();
+ },
+ _displayFileSize: function(id, loadedSize, totalSize) {
+ var item = this.getItemByFileId(id),
+ size = this.getSize(id),
+ sizeForDisplay = this._formatSize(size),
+ sizeEl = this._find(item, 'size');
+
+ if (loadedSize !== undefined && totalSize !== undefined) {
+ sizeForDisplay = this._formatProgress(loadedSize, totalSize);
+ }
+
+ qq(sizeEl).css({display: 'inline'});
+ qq(sizeEl).setText(sizeForDisplay);
+ },
+ _formatProgress: function (uploadedSize, totalSize) {
+ var message = this._options.text.formatProgress;
+ function r(name, replacement) { message = message.replace(name, replacement); }
+
+ r('{percent}', Math.round(uploadedSize / totalSize * 100));
+ r('{total_size}', this._formatSize(totalSize));
+ return message;
+ },
+ _controlFailureTextDisplay: function(item, response) {
+ var mode, maxChars, responseProperty, failureReason, shortFailureReason;
+
+ mode = this._options.failedUploadTextDisplay.mode;
+ maxChars = this._options.failedUploadTextDisplay.maxChars;
+ responseProperty = this._options.failedUploadTextDisplay.responseProperty;
+
+ if (mode === 'custom') {
+ failureReason = response[responseProperty];
+ if (failureReason) {
+ if (failureReason.length > maxChars) {
+ shortFailureReason = failureReason.substring(0, maxChars) + '...';
+ }
+ }
+ else {
+ failureReason = this._options.text.failUpload;
+ this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
+ }
+
+ qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
+
+ if (this._options.failedUploadTextDisplay.enableTooltip) {
+ this._showTooltip(item, failureReason);
+ }
+ }
+ else if (mode === 'default') {
+ qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
+ }
+ else if (mode !== 'none') {
+ this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
+ }
+ },
+ _showTooltip: function(item, text) {
+ item.title = text;
+ },
+ _showSpinner: function(id) {
+ var item = this.getItemByFileId(id),
+ spinnerEl = this._find(item, 'spinner');
+
+ spinnerEl.style.display = "inline-block";
+ },
+ _showCancelLink: function(item) {
+ if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
+ var cancelLink = this._find(item, 'cancel');
+
+ qq(cancelLink).css({display: 'inline'});
+ }
+ },
+ _showDeleteLink: function(id) {
+ var item = this.getItemByFileId(id),
+ deleteLink = this._find(item, 'deleteButton');
+
+ qq(deleteLink).css({display: 'inline'});
+ },
+ _itemError: function(code, name){
+ var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _batchError: function(message) {
+ qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
+ this._options.showMessage(message);
+ },
+ _setupPastePrompt: function() {
+ var self = this;
+
+ this._options.callbacks.onPasteReceived = function() {
+ var message = self._options.paste.namePromptMessage,
+ defaultVal = self._options.paste.defaultName;
+
+ return self._options.showPrompt(message, defaultVal);
+ };
+ },
+ _fileOrBlobRejected: function(id, name) {
+ this._totalFilesInBatch -= 1;
+ qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this, arguments);
+ },
+ _prepareItemsForUpload: function(items, params, endpoint) {
+ this._totalFilesInBatch = items.length;
+ this._filesInBatchAddedToUi = 0;
+ qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this, arguments);
+ }
+});
+;/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.AjaxRequestor = function (o) {
+ "use strict";
+
+ var log, shouldParamsBeInQueryString,
+ queue = [],
+ requestState = [],
+ options = {
+ method: 'POST',
+ maxConnections: 3,
+ customHeaders: {},
+ endpointStore: {},
+ paramsStore: {},
+ mandatedParams: {},
+ successfulResponseCodes: {
+ "DELETE": [200, 202, 204],
+ "POST": [200, 204]
+ },
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function (str, level) {},
+ onSend: function (id) {},
+ onComplete: function (id, xhrOrXdr, isError) {},
+ onCancel: function (id) {}
+ };
+
+ qq.extend(options, o);
+ log = options.log;
+ shouldParamsBeInQueryString = options.method === 'GET' || options.method === 'DELETE';
+
+
+ // [Simple methods](http://www.w3.org/TR/cors/#simple-method)
+ // are defined by the W3C in the CORS spec as a list of methods that, in part,
+ // make a CORS request eligible to be exempt from preflighting.
+ function isSimpleMethod() {
+ return qq.indexOf(["GET", "POST", "HEAD"], options.method) >= 0;
+ }
+
+ // [Simple headers](http://www.w3.org/TR/cors/#simple-header)
+ // are defined by the W3C in the CORS spec as a list of headers that, in part,
+ // make a CORS request eligible to be exempt from preflighting.
+ function containsNonSimpleHeaders(headers) {
+ var containsNonSimple = false;
+
+ qq.each(containsNonSimple, function(idx, header) {
+ if (qq.indexOf(["Accept", "Accept-Language", "Content-Language", "Content-Type"], header) < 0) {
+ containsNonSimple = true;
+ return false;
+ }
+ });
+
+ return containsNonSimple;
+ }
+
+ function isXdr(xhr) {
+ //The `withCredentials` test is a commonly accepted way to determine if XHR supports CORS.
+ return options.cors.expected && xhr.withCredentials === undefined;
+ }
+
+ // Returns either a new `XMLHttpRequest` or `XDomainRequest` instance.
+ function getCorsAjaxTransport() {
+ var xhrOrXdr;
+
+ if (window.XMLHttpRequest) {
+ xhrOrXdr = new XMLHttpRequest();
+
+ if (xhrOrXdr.withCredentials === undefined) {
+ xhrOrXdr = new XDomainRequest();
+ }
+ }
+
+ return xhrOrXdr;
+ }
+
+ // Returns either a new XHR/XDR instance, or an existing one for the associated `File` or `Blob`.
+ function getXhrOrXdr(id, dontCreateIfNotExist) {
+ var xhrOrXdr = requestState[id].xhr;
+
+ if (!xhrOrXdr && !dontCreateIfNotExist) {
+ if (options.cors.expected) {
+ xhrOrXdr = getCorsAjaxTransport();
+ }
+ else {
+ xhrOrXdr = new XMLHttpRequest();
+ }
+
+ requestState[id].xhr = xhrOrXdr;
+ }
+
+ return xhrOrXdr;
+ }
+
+ // Removes element from queue, sends next request
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ delete requestState[id];
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max) {
+ nextId = queue[max - 1];
+ sendRequest(nextId);
+ }
+ }
+
+ function onComplete(id, xdrError) {
+ var xhr = getXhrOrXdr(id),
+ method = options.method,
+ isError = xdrError === false;
+
+ dequeue(id);
+
+ if (isError) {
+ log(method + " request for " + id + " has failed", "error");
+ }
+ else if (!isXdr(xhr) && !isResponseSuccessful(xhr.status)) {
+ isError = true;
+ log(method + " request for " + id + " has failed - response code " + xhr.status, "error");
+ }
+
+ options.onComplete(id, xhr, isError);
+ }
+
+ function getParams(id) {
+ var params = {},
+ additionalParams = requestState[id].additionalParams,
+ mandatedParams = options.mandatedParams;
+
+ if (options.paramsStore.getParams) {
+ params = options.paramsStore.getParams(id);
+ }
+
+ if (additionalParams) {
+ qq.each(additionalParams, function (name, val) {
+ params[name] = val;
+ });
+ }
+
+ if (mandatedParams) {
+ qq.each(mandatedParams, function (name, val) {
+ params[name] = val;
+ });
+ }
+
+ return params;
+ }
+
+ function sendRequest(id) {
+ var xhr = getXhrOrXdr(id),
+ method = options.method,
+ params = getParams(id),
+ url;
+
+ options.onSend(id);
+
+ url = createUrl(id, params);
+
+ // XDR and XHR status detection APIs differ a bit.
+ if (isXdr(xhr)) {
+ xhr.onload = getXdrLoadHandler(id);
+ xhr.onerror = getXdrErrorHandler(id);
+ }
+ else {
+ xhr.onreadystatechange = getXhrReadyStateChangeHandler(id);
+ }
+
+ // The last parameter is assumed to be ignored if we are actually using `XDomainRequest`.
+ xhr.open(method, url, true);
+
+ // Instruct the transport to send cookies along with the CORS request,
+ // unless we are using `XDomainRequest`, which is not capable of this.
+ if (options.cors.expected && options.cors.sendCredentials && !isXdr(xhr)) {
+ xhr.withCredentials = true;
+ }
+
+ setHeaders(id);
+
+ log('Sending ' + method + " request for " + id);
+ if (!shouldParamsBeInQueryString && params) {
+ xhr.send(qq.obj2url(params, ""));
+ }
+ else {
+ xhr.send();
+ }
+ }
+
+ function createUrl(id, params) {
+ var endpoint = options.endpointStore.getEndpoint(id),
+ addToPath = requestState[id].addToPath;
+
+ if (addToPath != undefined) {
+ endpoint += "/" + addToPath;
+ }
+
+ if (shouldParamsBeInQueryString && params) {
+ return qq.obj2url(params, endpoint);
+ }
+ else {
+ return endpoint;
+ }
+ }
+
+ // Invoked by the UA to indicate a number of possible states that describe
+ // a live `XMLHttpRequest` transport.
+ function getXhrReadyStateChangeHandler(id) {
+ return function () {
+ if (getXhrOrXdr(id).readyState === 4) {
+ onComplete(id);
+ }
+ };
+ }
+
+ // This will be called by IE to indicate **success** for an associated
+ // `XDomainRequest` transported request.
+ function getXdrLoadHandler(id) {
+ return function () {
+ onComplete(id);
+ }
+ }
+
+ // This will be called by IE to indicate **failure** for an associated
+ // `XDomainRequest` transported request.
+ function getXdrErrorHandler(id) {
+ return function () {
+ onComplete(id, true);
+ }
+ }
+
+ function setHeaders(id) {
+ var xhr = getXhrOrXdr(id),
+ customHeaders = options.customHeaders;
+
+ // If this is a CORS request and a simple method with simple headers are used
+ // on an `XMLHttpRequest`, exclude these specific non-simple headers
+ // in an attempt to prevent preflighting. `XDomainRequest` does not support setting
+ // request headers, so we will take this into account as well.
+ if (isXdr(xhr)) {
+ if (!options.cors.expected || (!isSimpleMethod() || containsNonSimpleHeaders(customHeaders))) {
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+ }
+ }
+
+ // Assuming that all POST and PUT requests will need to be URL encoded.
+ // The payload of a POST `XDomainRequest` also needs to be URL encoded, but we
+ // can't set the Content-Type when using this transport.
+ if ((options.method === "POST" || options.method === "PUT") && !isXdr(xhr)) {
+ xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+ }
+
+ // `XDomainRequest` doesn't allow you to set any headers.
+ if (!isXdr(xhr)) {
+ qq.each(customHeaders, function (name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+ }
+
+ function cancelRequest(id) {
+ var xhr = getXhrOrXdr(id, true),
+ method = options.method;
+
+ if (xhr) {
+ // The event handlers we remove/unregister is dependant on whether we are
+ // using `XDomainRequest` or `XMLHttpRequest`.
+ if (isXdr(xhr)) {
+ xhr.onerror = null;
+ xhr.onload = null;
+ }
+ else {
+ xhr.onreadystatechange = null;
+ }
+
+ xhr.abort();
+ dequeue(id);
+
+ log('Cancelled ' + method + " for " + id);
+ options.onCancel(id);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function isResponseSuccessful(responseCode) {
+ return qq.indexOf(options.successfulResponseCodes[options.method], responseCode) >= 0;
+ }
+
+ return {
+ send: function (id, addToPath, additionalParams) {
+ requestState[id] = {
+ addToPath: addToPath,
+ additionalParams: additionalParams
+ };
+
+ var len = queue.push(id);
+
+ // if too many active connections, wait...
+ if (len <= options.maxConnections) {
+ sendRequest(id);
+ }
+ },
+ cancel: function (id) {
+ return cancelRequest(id);
+ }
+ };
+};
+;/** Generic class for sending non-upload ajax requests and handling the associated responses **/
+/*globals qq, XMLHttpRequest*/
+qq.DeleteFileAjaxRequestor = function(o) {
+ "use strict";
+
+ var requestor,
+ validMethods = ["POST", "DELETE"],
+ options = {
+ method: "DELETE",
+ uuidParamName: "qquuid",
+ endpointStore: {},
+ maxConnections: 3,
+ customHeaders: {},
+ paramsStore: {},
+ demoMode: false,
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ log: function(str, level) {},
+ onDelete: function(id) {},
+ onDeleteComplete: function(id, xhrOrXdr, isError) {}
+ };
+
+ qq.extend(options, o);
+
+ if (qq.indexOf(validMethods, getNormalizedMethod()) < 0) {
+ throw new Error("'" + getNormalizedMethod() + "' is not a supported method for delete file requests!");
+ }
+
+ function getNormalizedMethod() {
+ return options.method.toUpperCase();
+ }
+
+ function getMandatedParams() {
+ if (getNormalizedMethod() === "POST") {
+ return {
+ "_method": "DELETE"
+ };
+ }
+
+ return {};
+ }
+
+ requestor = new qq.AjaxRequestor({
+ method: getNormalizedMethod(),
+ endpointStore: options.endpointStore,
+ paramsStore: options.paramsStore,
+ mandatedParams: getMandatedParams(),
+ maxConnections: options.maxConnections,
+ customHeaders: options.customHeaders,
+ demoMode: options.demoMode,
+ log: options.log,
+ onSend: options.onDelete,
+ onComplete: options.onDeleteComplete,
+ cors: options.cors
+ });
+
+
+ return {
+ sendDelete: function(id, uuid) {
+ var additionalOptions = {};
+
+ options.log("Submitting delete file request for " + id);
+
+ if (getNormalizedMethod() === "DELETE") {
+ requestor.send(id, uuid);
+ }
+ else {
+ additionalOptions[options.uuidParamName] = uuid;
+ requestor.send(id, null, additionalOptions);
+ }
+ }
+ };
+};
+;qq.WindowReceiveMessage = function(o) {
+ var options = {
+ log: function(message, level) {}
+ },
+ callbackWrapperDetachers = {};
+
+ qq.extend(options, o);
+
+ return {
+ receiveMessage : function(id, callback) {
+ var onMessageCallbackWrapper = function(event) {
+ callback(event.data);
+ };
+
+ if (window.postMessage) {
+ callbackWrapperDetachers[id] = qq(window).attach("message", onMessageCallbackWrapper);
+ }
+ else {
+ log("iframe message passing not supported in this browser!", "error");
+ }
+ },
+
+ stopReceivingMessages : function(id) {
+ if (window.postMessage) {
+ var detacher = callbackWrapperDetachers[id];
+ if (detacher) {
+ detacher();
+ }
+ }
+ }
+ };
+};
+;/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+/*globals qq*/
+qq.UploadHandler = function(o) {
+ "use strict";
+
+ var queue = [],
+ options, log, handlerImpl, api;
+
+ // Default options, can be overridden by the user
+ options = {
+ debug: false,
+ forceMultipart: true,
+ paramsInBody: false,
+ paramsStore: {},
+ endpointStore: {},
+ filenameParam: 'qqfilename',
+ cors: {
+ expected: false,
+ sendCredentials: false
+ },
+ maxConnections: 3, // maximum number of concurrent uploads
+ uuidParamName: 'qquuid',
+ totalFileSizeParamName: 'qqtotalfilesize',
+ chunking: {
+ enabled: false,
+ partSize: 2000000, //bytes
+ paramNames: {
+ partIndex: 'qqpartindex',
+ partByteOffset: 'qqpartbyteoffset',
+ chunkSize: 'qqchunksize',
+ totalParts: 'qqtotalparts',
+ filename: 'qqfilename'
+ }
+ },
+ resume: {
+ enabled: false,
+ id: null,
+ cookiesExpireIn: 7, //days
+ paramNames: {
+ resuming: "qqresume"
+ }
+ },
+ log: function(str, level) {},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response, xhr){},
+ onCancel: function(id, fileName){},
+ onUpload: function(id, fileName){},
+ onUploadChunk: function(id, fileName, chunkData){},
+ onAutoRetry: function(id, fileName, response, xhr){},
+ onResume: function(id, fileName, chunkData){},
+ onUuidChanged: function(id, newUuid){}
+
+ };
+ qq.extend(options, o);
+
+ log = options.log;
+
+ /**
+ * Removes element from queue, starts upload of next
+ */
+ function dequeue(id) {
+ var i = qq.indexOf(queue, id),
+ max = options.maxConnections,
+ nextId;
+
+ if (i >= 0) {
+ queue.splice(i, 1);
+
+ if (queue.length >= max && i < max){
+ nextId = queue[max-1];
+ handlerImpl.upload(nextId);
+ }
+ }
+ };
+
+ if (qq.supportedFeatures.ajaxUploading) {
+ handlerImpl = new qq.UploadHandlerXhr(options, dequeue, options.onUuidChanged, log);
+ }
+ else {
+ handlerImpl = new qq.UploadHandlerForm(options, dequeue, options.onUuidChanged, log);
+ }
+
+ function cancelSuccess(id) {
+ log('Cancelling ' + id);
+ options.paramsStore.remove(id);
+ dequeue(id);
+ }
+
+
+ api = {
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){
+ return handlerImpl.add(file);
+ },
+ /**
+ * Sends the file identified by id
+ */
+ upload: function(id){
+ var len = queue.push(id);
+
+ // if too many active uploads, wait...
+ if (len <= options.maxConnections){
+ handlerImpl.upload(id);
+ return true;
+ }
+
+ return false;
+ },
+ retry: function(id) {
+ var i = qq.indexOf(queue, id);
+ if (i >= 0) {
+ return handlerImpl.upload(id, true);
+ }
+ else {
+ return this.upload(id);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id) {
+ var cancelRetVal = handlerImpl.cancel(id);
+
+ if (qq.isPromise(cancelRetVal)) {
+ cancelRetVal.then(function() {
+ cancelSuccess(id);
+ });
+ }
+ else if (cancelRetVal !== false) {
+ cancelSuccess(id);
+ }
+ },
+ /**
+ * Cancels all queued or in-progress uploads
+ */
+ cancelAll: function() {
+ var self = this,
+ queueCopy = [];
+
+ qq.extend(queueCopy, queue);
+ qq.each(queueCopy, function(idx, fileId) {
+ self.cancel(fileId);
+ });
+
+ queue = [];
+ },
+ /**
+ * Returns name of the file identified by id
+ */
+ getName: function(id) {
+ return handlerImpl.getName(id);
+ },
+ // Update/change the name of the associated file.
+ // This updated name should be sent as a parameter.
+ setName: function(id, newName) {
+ handlerImpl.setName(id, newName);
+ },
+ /**
+ * Returns size of the file identified by id
+ */
+ getSize: function(id){
+ if (handlerImpl.getSize) {
+ return handlerImpl.getSize(id);
+ }
+ },
+ getFile: function(id) {
+ if (handlerImpl.getFile) {
+ return handlerImpl.getFile(id);
+ }
+ },
+ reset: function() {
+ log('Resetting upload handler');
+ api.cancelAll();
+ queue = [];
+ handlerImpl.reset();
+ },
+ expunge: function(id) {
+ return handlerImpl.expunge(id);
+ },
+ getUuid: function(id) {
+ return handlerImpl.getUuid(id);
+ },
+ /**
+ * Determine if the file exists.
+ */
+ isValid: function(id) {
+ return handlerImpl.isValid(id);
+ },
+ getResumableFilesData: function() {
+ if (handlerImpl.getResumableFilesData) {
+ return handlerImpl.getResumableFilesData();
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+;/*globals qq, document, setTimeout*/
+/*globals clearTimeout*/
+qq.UploadHandlerForm = function(o, uploadCompleteCallback, onUuidChanged, logCallback) {
+ "use strict";
+
+ var options = o,
+ inputs = [],
+ uuids = [],
+ newNames = [],
+ detachLoadEvents = {},
+ postMessageCallbackTimers = {},
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
+ onloadCallbacks = {},
+ formHandlerInstanceId = qq.getUniqueId(),
+ api;
+
+
+ function detachLoadEvent(id) {
+ if (detachLoadEvents[id] !== undefined) {
+ detachLoadEvents[id]();
+ delete detachLoadEvents[id];
+ }
+ }
+
+ function registerPostMessageCallback(iframe, callback) {
+ var iframeName = iframe.id,
+ fileId = getFileIdForIframeName(iframeName);
+
+ onloadCallbacks[uuids[fileId]] = callback;
+
+ detachLoadEvents[fileId] = qq(iframe).attach('load', function() {
+ if (inputs[fileId]) {
+ log("Received iframe load event for CORS upload request (iframe name " + iframeName + ")");
+
+ postMessageCallbackTimers[iframeName] = setTimeout(function() {
+ var errorMessage = "No valid message received from loaded iframe for iframe name " + iframeName;
+ log(errorMessage, "error");
+ callback({
+ error: errorMessage
+ });
+ }, 1000);
+ }
+ });
+
+ corsMessageReceiver.receiveMessage(iframeName, function(message) {
+ log("Received the following window message: '" + message + "'");
+ var response = parseResponse(getFileIdForIframeName(iframeName), message),
+ uuid = response.uuid,
+ onloadCallback;
+
+ if (uuid && onloadCallbacks[uuid]) {
+ log("Handling response for iframe name " + iframeName);
+ clearTimeout(postMessageCallbackTimers[iframeName]);
+ delete postMessageCallbackTimers[iframeName];
+
+ detachLoadEvent(iframeName);
+
+ onloadCallback = onloadCallbacks[uuid];
+
+ delete onloadCallbacks[uuid];
+ corsMessageReceiver.stopReceivingMessages(iframeName);
+ onloadCallback(response);
+ }
+ else if (!uuid) {
+ log("'" + message + "' does not contain a UUID - ignoring.");
+ }
+ });
+ }
+
+ function attachLoadEvent(iframe, callback) {
+ /*jslint eqeq: true*/
+
+ if (options.cors.expected) {
+ registerPostMessageCallback(iframe, callback);
+ }
+ else {
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
+ log('Received response for ' + iframe.id);
+
+ // when we remove iframe from dom
+ // the request stops, but in IE load
+ // event fires
+ if (!iframe.parentNode){
+ return;
+ }
+
+ try {
+ // fixing Opera 10.53
+ if (iframe.contentDocument &&
+ iframe.contentDocument.body &&
+ iframe.contentDocument.body.innerHTML == "false"){
+ // In Opera event is fired second time
+ // when body.innerHTML changed from false
+ // to server response approx. after 1 sec
+ // when we upload file with iframe
+ return;
+ }
+ }
+ catch (error) {
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
+ }
+
+ callback();
+ });
+ }
+ }
+
+ /**
+ * Returns json object received by iframe from server.
+ */
+ function getIframeContentJson(id, iframe) {
+ /*jshint evil: true*/
+
+ var response;
+
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
+ try {
+ // iframe.contentWindow.document - for IE<7
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
+ innerHtml = doc.body.innerHTML;
+
+ log("converting iframe's innerHTML to JSON");
+ log("innerHTML = " + innerHtml);
+ //plain text response may be wrapped in tag
+ if (innerHtml && innerHtml.match(/^ ');
+
+ iframe.setAttribute('id', iframeName);
+
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ return iframe;
+ }
+
+ /**
+ * Creates form, that will be submitted to iframe
+ */
+ function createForm(id, iframe){
+ var params = options.paramsStore.getParams(id),
+ protocol = options.demoMode ? "GET" : "POST",
+ form = qq.toElement(' '),
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint;
+
+ params[options.uuidParamName] = uuids[id];
+
+ if (newNames[id] !== undefined) {
+ params[options.filenameParam] = newNames[id];
+ }
+
+ if (!options.paramsInBody) {
+ url = qq.obj2url(params, endpoint);
+ }
+ else {
+ qq.obj2Inputs(params, form);
+ }
+
+ form.setAttribute('action', url);
+ form.setAttribute('target', iframe.name);
+ form.style.display = 'none';
+ document.body.appendChild(form);
+
+ return form;
+ }
+
+ function expungeFile(id) {
+ delete inputs[id];
+ delete uuids[id];
+ delete detachLoadEvents[id];
+
+ if (options.cors.expected) {
+ clearTimeout(postMessageCallbackTimers[id]);
+ delete postMessageCallbackTimers[id];
+ corsMessageReceiver.stopReceivingMessages(id);
+ }
+
+ var iframe = document.getElementById(getIframeName(id));
+ if (iframe) {
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
+
+ qq(iframe).remove();
+ }
+ }
+
+ function getFileIdForIframeName(iframeName) {
+ return iframeName.split("_")[0];
+ }
+
+ function getIframeName(fileId) {
+ return fileId + "_" + formHandlerInstanceId;
+ }
+
+
+ api = {
+ add: function(fileInput) {
+ fileInput.setAttribute('name', options.inputName);
+
+ var id = inputs.push(fileInput) - 1;
+ uuids[id] = qq.getUniqueId();
+
+ // remove file input from DOM
+ if (fileInput.parentNode){
+ qq(fileInput).remove();
+ }
+
+ return id;
+ },
+ getName: function(id) {
+ /*jslint regexp: true*/
+
+ if (newNames[id] !== undefined) {
+ return newNames[id];
+ }
+ else if (api.isValid(id)) {
+ // get input value and remove path to normalize
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ setName: function(id, newName) {
+ newNames[id] = newName;
+ },
+ isValid: function(id) {
+ return inputs[id] !== undefined;
+ },
+ reset: function() {
+ inputs = [];
+ uuids = [];
+ newNames = [];
+ detachLoadEvents = {};
+ formHandlerInstanceId = qq.getUniqueId();
+ },
+ expunge: function(id) {
+ return expungeFile(id);
+ },
+ getUuid: function(id) {
+ return uuids[id];
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, api.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeFile(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeFile(id);
+ return true;
+ }
+
+ return false;
+ },
+
+ upload: function(id) {
+ var input = inputs[id],
+ fileName = api.getName(id),
+ iframe = createIframe(id),
+ form;
+
+ if (!input){
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
+ }
+
+ options.onUpload(id, api.getName(id));
+
+ form = createForm(id, iframe);
+ form.appendChild(input);
+
+ attachLoadEvent(iframe, function(responseFromMessage){
+ log('iframe loaded');
+
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(id, iframe);
+
+ detachLoadEvent(id);
+
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
+ if (!options.cors.expected) {
+ qq(iframe).remove();
+ }
+
+ if (!response.success) {
+ if (options.onAutoRetry(id, fileName, response)) {
+ return;
+ }
+ }
+ options.onComplete(id, fileName, response);
+ uploadComplete(id);
+ });
+
+ log('Sending upload request for ' + id);
+ form.submit();
+ qq(form).remove();
+ }
+ };
+
+ return api;
+};
+;/*globals qq, File, XMLHttpRequest, FormData, Blob*/
+qq.UploadHandlerXhr = function(o, uploadCompleteCallback, onUuidChanged, logCallback) {
+ "use strict";
+
+ var options = o,
+ uploadComplete = uploadCompleteCallback,
+ log = logCallback,
+ fileState = [],
+ cookieItemDelimiter = "|",
+ chunkFiles = options.chunking.enabled && qq.supportedFeatures.chunking,
+ resumeEnabled = options.resume.enabled && chunkFiles && qq.supportedFeatures.resume,
+ resumeId = getResumeId(),
+ multipart = options.forceMultipart || options.paramsInBody,
+ api;
+
+
+ function addChunkingSpecificParams(id, params, chunkData) {
+ var size = api.getSize(id),
+ name = api.getName(id);
+
+ params[options.chunking.paramNames.partIndex] = chunkData.part;
+ params[options.chunking.paramNames.partByteOffset] = chunkData.start;
+ params[options.chunking.paramNames.chunkSize] = chunkData.size;
+ params[options.chunking.paramNames.totalParts] = chunkData.count;
+ params[options.totalFileSizeParamName] = size;
+
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ if (multipart) {
+ params[options.filenameParam] = name;
+ }
+ }
+
+ function addResumeSpecificParams(params) {
+ params[options.resume.paramNames.resuming] = true;
+ }
+
+ function getChunk(fileOrBlob, startByte, endByte) {
+ if (fileOrBlob.slice) {
+ return fileOrBlob.slice(startByte, endByte);
+ }
+ else if (fileOrBlob.mozSlice) {
+ return fileOrBlob.mozSlice(startByte, endByte);
+ }
+ else if (fileOrBlob.webkitSlice) {
+ return fileOrBlob.webkitSlice(startByte, endByte);
+ }
+ }
+
+ function getChunkData(id, chunkIndex) {
+ var chunkSize = options.chunking.partSize,
+ fileSize = api.getSize(id),
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ startBytes = chunkSize * chunkIndex,
+ endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
+ totalChunks = getTotalChunks(id);
+
+ return {
+ part: chunkIndex,
+ start: startBytes,
+ end: endBytes,
+ count: totalChunks,
+ blob: getChunk(fileOrBlob, startBytes, endBytes),
+ size: endBytes - startBytes
+ };
+ }
+
+ function getTotalChunks(id) {
+ var fileSize = api.getSize(id),
+ chunkSize = options.chunking.partSize;
+
+ return Math.ceil(fileSize / chunkSize);
+ }
+
+ function createXhr(id) {
+ var xhr = new XMLHttpRequest();
+
+ fileState[id].xhr = xhr;
+
+ return xhr;
+ }
+
+ function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
+ var formData = new FormData(),
+ method = options.demoMode ? "GET" : "POST",
+ endpoint = options.endpointStore.getEndpoint(id),
+ url = endpoint,
+ name = api.getName(id),
+ size = api.getSize(id),
+ blobData = fileState[id].blobData,
+ newName = fileState[id].newName;
+
+ params[options.uuidParamName] = fileState[id].uuid;
+
+ if (multipart) {
+ params[options.totalFileSizeParamName] = size;
+
+ if (blobData) {
+ /**
+ * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
+ * or an empty string. So, we will need to include the actual file name as a param in this case.
+ */
+ params[options.filenameParam] = blobData.name;
+ }
+ }
+
+ if (newName !== undefined) {
+ params[options.filenameParam] = newName;
+ }
+
+ //build query string
+ if (!options.paramsInBody) {
+ if (!multipart) {
+ params[options.inputName] = newName || name;
+ }
+ url = qq.obj2url(params, endpoint);
+ }
+
+ xhr.open(method, url, true);
+
+ if (options.cors.expected && options.cors.sendCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (multipart) {
+ if (options.paramsInBody) {
+ qq.obj2FormData(params, formData);
+ }
+
+ formData.append(options.inputName, fileOrBlob);
+ return formData;
+ }
+
+ return fileOrBlob;
+ }
+
+ function setHeaders(id, xhr) {
+ var extraHeaders = options.customHeaders,
+ fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+
+ if (!multipart) {
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
+ xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
+ }
+
+ qq.each(extraHeaders, function(name, val) {
+ xhr.setRequestHeader(name, val);
+ });
+ }
+
+ function handleCompletedItem(id, response, xhr) {
+ var name = api.getName(id),
+ size = api.getSize(id);
+
+ fileState[id].attemptingResume = false;
+
+ options.onProgress(id, name, size, size);
+ options.onComplete(id, name, response, xhr);
+
+ if (fileState[id]) {
+ delete fileState[id].xhr;
+ }
+
+ uploadComplete(id);
+ }
+
+ function uploadNextChunk(id) {
+ var chunkIdx = fileState[id].remainingChunkIdxs[0],
+ chunkData = getChunkData(id, chunkIdx),
+ xhr = createXhr(id),
+ size = api.getSize(id),
+ name = api.getName(id),
+ toSend, params;
+
+ if (fileState[id].loaded === undefined) {
+ fileState[id].loaded = 0;
+ }
+
+ if (resumeEnabled && fileState[id].file) {
+ persistChunkData(id, chunkData);
+ }
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ var totalLoaded = e.loaded + fileState[id].loaded,
+ estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
+
+ options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
+ }
+ };
+
+ options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
+
+ params = options.paramsStore.getParams(id);
+ addChunkingSpecificParams(id, params, chunkData);
+
+ if (fileState[id].attemptingResume) {
+ addResumeSpecificParams(params);
+ }
+
+ toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
+ setHeaders(id, xhr);
+
+ log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
+ xhr.send(toSend);
+ }
+
+ function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
+ var chunkData = getChunkData(id, chunkIdx),
+ blobSize = chunkData.size,
+ overhead = requestSize - blobSize,
+ size = api.getSize(id),
+ chunkCount = chunkData.count,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ overheadDiff = overhead - initialRequestOverhead;
+
+ fileState[id].lastRequestOverhead = overhead;
+
+ if (chunkIdx === 0) {
+ fileState[id].lastChunkIdxProgress = 0;
+ fileState[id].initialRequestOverhead = overhead;
+ fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
+ }
+ else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
+ fileState[id].lastChunkIdxProgress = chunkIdx;
+ fileState[id].estTotalRequestsSize += overheadDiff;
+ }
+
+ return fileState[id].estTotalRequestsSize;
+ }
+
+ function getLastRequestOverhead(id) {
+ if (multipart) {
+ return fileState[id].lastRequestOverhead;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ function handleSuccessfullyCompletedChunk(id, response, xhr) {
+ var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
+ chunkData = getChunkData(id, chunkIdx);
+
+ fileState[id].attemptingResume = false;
+ fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
+
+ if (fileState[id].remainingChunkIdxs.length > 0) {
+ uploadNextChunk(id);
+ }
+ else {
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function isErrorResponse(xhr, response) {
+ return xhr.status !== 200 || !response.success || response.reset;
+ }
+
+ function parseResponse(id, xhr) {
+ var response;
+
+ try {
+ response = qq.parseJson(xhr.responseText);
+
+ if (response.newUuid !== undefined) {
+ log("Server requested UUID change from '" + fileState[id].uuid + "' to '" + response.newUuid + "'");
+ fileState[id].uuid = response.newUuid;
+ onUuidChanged(id, response.newUuid);
+ }
+ }
+ catch(error) {
+ log('Error when attempting to parse xhr response text (' + error + ')', 'error');
+ response = {};
+ }
+
+ return response;
+ }
+
+ function handleResetResponse(id) {
+ log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ fileState[id].attemptingResume = false;
+ }
+
+ fileState[id].remainingChunkIdxs = [];
+ delete fileState[id].loaded;
+ delete fileState[id].estTotalRequestsSize;
+ delete fileState[id].initialRequestOverhead;
+ }
+
+ function handleResetResponseOnResumeAttempt(id) {
+ fileState[id].attemptingResume = false;
+ log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
+ handleResetResponse(id);
+ api.upload(id, true);
+ }
+
+ function handleNonResetErrorResponse(id, response, xhr) {
+ var name = api.getName(id);
+
+ if (options.onAutoRetry(id, name, response, xhr)) {
+ return;
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function onComplete(id, xhr) {
+ var response;
+
+ // the request was aborted/cancelled
+ if (!fileState[id]) {
+ return;
+ }
+
+ log("xhr - server response received for " + id);
+ log("responseText = " + xhr.responseText);
+ response = parseResponse(id, xhr);
+
+ if (isErrorResponse(xhr, response)) {
+ if (response.reset) {
+ handleResetResponse(id);
+ }
+
+ if (fileState[id].attemptingResume && response.reset) {
+ handleResetResponseOnResumeAttempt(id);
+ }
+ else {
+ handleNonResetErrorResponse(id, response, xhr);
+ }
+ }
+ else if (chunkFiles) {
+ handleSuccessfullyCompletedChunk(id, response, xhr);
+ }
+ else {
+ handleCompletedItem(id, response, xhr);
+ }
+ }
+
+ function getChunkDataForCallback(chunkData) {
+ return {
+ partIndex: chunkData.part,
+ startByte: chunkData.start + 1,
+ endByte: chunkData.end,
+ totalParts: chunkData.count
+ };
+ }
+
+ function getReadyStateChangeHandler(id, xhr) {
+ return function() {
+ if (xhr.readyState === 4) {
+ onComplete(id, xhr);
+ }
+ };
+ }
+
+ function persistChunkData(id, chunkData) {
+ var fileUuid = api.getUuid(id),
+ lastByteSent = fileState[id].loaded,
+ initialRequestOverhead = fileState[id].initialRequestOverhead,
+ estTotalRequestsSize = fileState[id].estTotalRequestsSize,
+ cookieName = getChunkDataCookieName(id),
+ cookieValue = fileUuid +
+ cookieItemDelimiter + chunkData.part +
+ cookieItemDelimiter + lastByteSent +
+ cookieItemDelimiter + initialRequestOverhead +
+ cookieItemDelimiter + estTotalRequestsSize,
+ cookieExpDays = options.resume.cookiesExpireIn;
+
+ qq.setCookie(cookieName, cookieValue, cookieExpDays);
+ }
+
+ function deletePersistedChunkData(id) {
+ if (fileState[id].file) {
+ var cookieName = getChunkDataCookieName(id);
+ qq.deleteCookie(cookieName);
+ }
+ }
+
+ function getPersistedChunkData(id) {
+ var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
+ filename = api.getName(id),
+ sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
+
+ if (chunkCookieValue) {
+ sections = chunkCookieValue.split(cookieItemDelimiter);
+
+ if (sections.length === 5) {
+ uuid = sections[0];
+ partIndex = parseInt(sections[1], 10);
+ lastByteSent = parseInt(sections[2], 10);
+ initialRequestOverhead = parseInt(sections[3], 10);
+ estTotalRequestsSize = parseInt(sections[4], 10);
+
+ return {
+ uuid: uuid,
+ part: partIndex,
+ lastByteSent: lastByteSent,
+ initialRequestOverhead: initialRequestOverhead,
+ estTotalRequestsSize: estTotalRequestsSize
+ };
+ }
+ else {
+ log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
+ }
+ }
+ }
+
+ function getChunkDataCookieName(id) {
+ var filename = api.getName(id),
+ fileSize = api.getSize(id),
+ maxChunkSize = options.chunking.partSize,
+ cookieName;
+
+ cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
+
+ if (resumeId !== undefined) {
+ cookieName += cookieItemDelimiter + resumeId;
+ }
+
+ return cookieName;
+ }
+
+ function getResumeId() {
+ if (options.resume.id !== null &&
+ options.resume.id !== undefined &&
+ !qq.isFunction(options.resume.id) &&
+ !qq.isObject(options.resume.id)) {
+
+ return options.resume.id;
+ }
+ }
+
+ function calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex) {
+ var currentChunkIndex;
+
+ for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
+ fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
+ }
+
+ uploadNextChunk(id);
+ }
+
+ function onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume) {
+ firstChunkIndex = persistedChunkInfoForResume.part;
+ fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
+ fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
+ fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
+ fileState[id].attemptingResume = true;
+ log('Resuming ' + name + " at partition index " + firstChunkIndex);
+
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+
+ function handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex) {
+ var name = api.getName(id),
+ firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part),
+ onResumeRetVal;
+
+ onResumeRetVal = options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume));
+ if (qq.isPromise(onResumeRetVal)) {
+ log("Waiting for onResume promise to be fulfilled for " + id);
+ onResumeRetVal.then(
+ function() {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ },
+ function() {
+ log("onResume promise fulfilled - failure indicated. Will not resume.")
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ );
+ }
+ else if (onResumeRetVal !== false) {
+ onResumeSuccess(id, name, firstChunkIndex, persistedChunkInfoForResume);
+ }
+ else {
+ log("onResume callback returned false. Will not resume.");
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+
+ function handleFileChunkingUpload(id, retry) {
+ var firstChunkIndex = 0,
+ persistedChunkInfoForResume;
+
+ if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
+ fileState[id].remainingChunkIdxs = [];
+
+ if (resumeEnabled && !retry && fileState[id].file) {
+ persistedChunkInfoForResume = getPersistedChunkData(id);
+ if (persistedChunkInfoForResume) {
+ handlePossibleResumeAttempt(id, persistedChunkInfoForResume, firstChunkIndex);
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ calculateRemainingChunkIdxsAndUpload(id, firstChunkIndex);
+ }
+ }
+ else {
+ uploadNextChunk(id);
+ }
+ }
+
+ function handleStandardFileUpload(id) {
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
+ name = api.getName(id),
+ xhr, params, toSend;
+
+ fileState[id].loaded = 0;
+
+ xhr = createXhr(id);
+
+ xhr.upload.onprogress = function(e){
+ if (e.lengthComputable){
+ fileState[id].loaded = e.loaded;
+ options.onProgress(id, name, e.loaded, e.total);
+ }
+ };
+
+ xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
+
+ params = options.paramsStore.getParams(id);
+ toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
+ setHeaders(id, xhr);
+
+ log('Sending upload request for ' + id);
+ xhr.send(toSend);
+ }
+
+ function expungeItem(id) {
+ var xhr = fileState[id].xhr;
+
+ if (xhr) {
+ xhr.onreadystatechange = null;
+ xhr.abort();
+ }
+
+ if (resumeEnabled) {
+ deletePersistedChunkData(id);
+ }
+
+ delete fileState[id];
+ }
+
+ api = {
+ /**
+ * Adds File or Blob to the queue
+ * Returns id to use with upload, cancel
+ **/
+ add: function(fileOrBlobData){
+ var id, persistedChunkData,
+ uuid = qq.getUniqueId();
+
+ if (qq.isFile(fileOrBlobData)) {
+ id = fileState.push({file: fileOrBlobData}) - 1;
+ }
+ else if (qq.isBlob(fileOrBlobData.blob)) {
+ id = fileState.push({blobData: fileOrBlobData}) - 1;
+ }
+ else {
+ throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
+ }
+
+ if (resumeEnabled) {
+ persistedChunkData = getPersistedChunkData(id);
+
+ if (persistedChunkData) {
+ uuid = persistedChunkData.uuid;
+ }
+ }
+
+ fileState[id].uuid = uuid;
+
+ return id;
+ },
+ getName: function(id) {
+ if (api.isValid(id)) {
+ var file = fileState[id].file,
+ blobData = fileState[id].blobData,
+ newName = fileState[id].newName;
+
+ if (newName !== undefined) {
+ return newName;
+ }
+ else if (file) {
+ // fix missing name in Safari 4
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
+ }
+ else {
+ return blobData.name;
+ }
+ }
+ else {
+ log(id + " is not a valid item ID.", "error");
+ }
+ },
+ setName: function(id, newName) {
+ fileState[id].newName = newName;
+ },
+ getSize: function(id) {
+ /*jshint eqnull: true*/
+ var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
+
+ if (qq.isFileOrInput(fileOrBlob)) {
+ return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
+ }
+ else {
+ return fileOrBlob.size;
+ }
+ },
+ getFile: function(id) {
+ if (fileState[id]) {
+ return fileState[id].file || fileState[id].blobData.blob;
+ }
+ },
+ isValid: function(id) {
+ return fileState[id] !== undefined;
+ },
+ reset: function() {
+ fileState = [];
+ },
+ expunge: function(id) {
+ return expungeItem(id);
+ },
+ getUuid: function(id) {
+ return fileState[id].uuid;
+ },
+ /**
+ * Sends the file identified by id to the server
+ */
+ upload: function(id, retry) {
+ var name = this.getName(id);
+
+ if (this.isValid(id)) {
+ options.onUpload(id, name);
+
+ if (chunkFiles) {
+ handleFileChunkingUpload(id, retry);
+ }
+ else {
+ handleStandardFileUpload(id);
+ }
+ }
+ },
+ cancel: function(id) {
+ var onCancelRetVal = options.onCancel(id, this.getName(id));
+
+ if (qq.isPromise(onCancelRetVal)) {
+ return onCancelRetVal.then(function() {
+ expungeItem(id);
+ });
+ }
+ else if (onCancelRetVal !== false) {
+ expungeItem(id);
+ return true;
+ }
+
+ return false;
+ },
+ getResumableFilesData: function() {
+ var matchingCookieNames = [],
+ resumableFilesData = [];
+
+ if (chunkFiles && resumeEnabled) {
+ if (resumeId === undefined) {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
+ }
+ else {
+ matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
+ cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
+ cookieItemDelimiter + resumeId + "="));
+ }
+
+ qq.each(matchingCookieNames, function(idx, cookieName) {
+ var cookiesNameParts = cookieName.split(cookieItemDelimiter);
+ var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
+
+ resumableFilesData.push({
+ name: decodeURIComponent(cookiesNameParts[1]),
+ size: cookiesNameParts[2],
+ uuid: cookieValueParts[0],
+ partIdx: cookieValueParts[1]
+ });
+ });
+
+ return resumableFilesData;
+ }
+ return [];
+ }
+ };
+
+ return api;
+};
+;// Base handler for UI (FineUploader mode) events.
+// Some more specific handlers inherit from this one.
+qq.UiEventHandler = function(s, protectedApi) {
+ "use strict";
+
+ var disposer = new qq.DisposeSupport(),
+ spec = {
+ eventType: 'click',
+ attachTo: null,
+ onHandled: function(target, event) {}
+ },
+ // This makes up the "public" API methods that will be accessible
+ // to instances constructing a base or child handler
+ publicApi = {
+ addHandler: function(element) {
+ addHandler(element);
+ },
+
+ dispose: function() {
+ disposer.dispose();
+ }
+ };
+
+
+
+ function addHandler(element) {
+ disposer.attach(element, spec.eventType, function(event) {
+ // Only in IE: the `event` is a property of the `window`.
+ event = event || window.event;
+
+ // On older browsers, we must check the `srcElement` instead of the `target`.
+ var target = event.target || event.srcElement;
+
+ spec.onHandled(target, event);
+ });
+ }
+
+ // These make up the "protected" API methods that children of this base handler will utilize.
+ qq.extend(protectedApi, {
+ // Find the ID of the associated file by looking for an
+ // expando property present on each file item in the DOM.
+ getItemFromEventTarget: function(target) {
+ var item = target.parentNode;
+
+ while(item.qqFileId === undefined) {
+ item = item.parentNode;
+ }
+
+ return item;
+ },
+
+ getFileIdFromItem: function(item) {
+ return item.qqFileId;
+ },
+
+ getDisposeSupport: function() {
+ return disposer;
+ }
+ });
+
+
+ qq.extend(spec, s);
+
+ if (spec.attachTo) {
+ addHandler(spec.attachTo);
+ }
+
+ return publicApi;
+};
+;qq.DeleteRetryOrCancelClickHandler = function(s) {
+ "use strict";
+
+ var inheritedInternalApi = {},
+ spec = {
+ listElement: document,
+ log: function(message, lvl) {},
+ classes: {
+ cancel: 'qq-upload-cancel',
+ deleteButton: 'qq-upload-delete',
+ retry: 'qq-upload-retry'
+ },
+ onDeleteFile: function(fileId) {},
+ onCancel: function(fileId) {},
+ onRetry: function(fileId) {},
+ onGetName: function(fileId) {}
+ };
+
+ function examineEvent(target, event) {
+ if (qq(target).hasClass(spec.classes.cancel)
+ || qq(target).hasClass(spec.classes.retry)
+ || qq(target).hasClass(spec.classes.deleteButton)) {
+
+ var item = inheritedInternalApi.getItemFromEventTarget(target),
+ fileId = inheritedInternalApi.getFileIdFromItem(item);
+
+ qq.preventDefault(event);
+
+ spec.log(qq.format("Detected valid cancel, retry, or delete click event on file '{}', ID: {}.", spec.onGetName(fileId), fileId));
+ deleteRetryOrCancel(target, fileId);
+ }
+ }
+
+ function deleteRetryOrCancel(target, fileId) {
+ if (qq(target).hasClass(spec.classes.deleteButton)) {
+ spec.onDeleteFile(fileId);
+ }
+ else if (qq(target).hasClass(spec.classes.cancel)) {
+ spec.onCancel(fileId);
+ }
+ else {
+ spec.onRetry(fileId);
+ }
+ }
+
+ qq.extend(spec, s);
+
+ spec.eventType = 'click';
+ spec.onHandled = examineEvent;
+ spec.attachTo = spec.listElement;
+
+ qq.extend(this, new qq.UiEventHandler(spec, inheritedInternalApi));
+};
+;// Handles edit-related events on a file item (FineUploader mode). This is meant to be a parent handler.
+// Children will delegate to this handler when specific edit-related actions are detected.
+qq.FilenameEditHandler = function(s, inheritedInternalApi) {
+ "use strict";
+
+ var spec = {
+ listElement: null,
+ log: function(message, lvl) {},
+ classes: {
+ file: 'qq-upload-file'
+ },
+ onGetUploadStatus: function(fileId) {},
+ onGetName: function(fileId) {},
+ onSetName: function(fileId, newName) {},
+ onGetInput: function(item) {},
+ onEditingStatusChange: function(fileId, isEditing) {}
+ },
+ publicApi;
+
+ function getFilenameSansExtension(fileId) {
+ var filenameSansExt = spec.onGetName(fileId),
+ extIdx = filenameSansExt.lastIndexOf('.');
+
+ if (extIdx > 0) {
+ filenameSansExt = filenameSansExt.substr(0, extIdx);
+ }
+
+ return filenameSansExt;
+ }
+
+ function getOriginalExtension(fileId) {
+ var origName = spec.onGetName(fileId),
+ extIdx = origName.lastIndexOf('.');
+
+ if (extIdx > 0) {
+ return origName.substr(extIdx, origName.length - extIdx);
+ }
+ }
+
+ // Callback iff the name has been changed
+ function handleNameUpdate(newFilenameInputEl, fileId) {
+ var newName = newFilenameInputEl.value,
+ origExtension;
+
+ if (newName !== undefined && qq.trimStr(newName).length > 0) {
+ origExtension = getOriginalExtension(fileId);
+
+ if (origExtension !== undefined) {
+ newName = newName + getOriginalExtension(fileId);
+ }
+
+ spec.onSetName(fileId, newName);
+ }
+
+ spec.onEditingStatusChange(fileId, false);
+ }
+
+ // The name has been updated if the filename edit input loses focus.
+ function registerInputBlurHandler(inputEl, fileId) {
+ inheritedInternalApi.getDisposeSupport().attach(inputEl, 'blur', function() {
+ handleNameUpdate(inputEl, fileId)
+ });
+ }
+
+ // The name has been updated if the user presses enter.
+ function registerInputEnterKeyHandler(inputEl, fileId) {
+ inheritedInternalApi.getDisposeSupport().attach(inputEl, 'keyup', function(event) {
+
+ var code = event.keyCode || event.which;
+
+ if (code === 13) {
+ handleNameUpdate(inputEl, fileId)
+ }
+ });
+ }
+
+ qq.extend(spec, s);
+
+ spec.attachTo = spec.listElement;
+
+ publicApi = qq.extend(this, new qq.UiEventHandler(spec, inheritedInternalApi));
+
+ qq.extend(inheritedInternalApi, {
+ handleFilenameEdit: function(fileId, target, item, focusInput) {
+ var newFilenameInputEl = spec.onGetInput(item);
+
+ spec.onEditingStatusChange(fileId, true);
+
+ newFilenameInputEl.value = getFilenameSansExtension(fileId);
+
+ if (focusInput) {
+ newFilenameInputEl.focus();
+ }
+
+ registerInputBlurHandler(newFilenameInputEl, fileId);
+ registerInputEnterKeyHandler(newFilenameInputEl, fileId);
+ }
+ });
+
+ return publicApi;
+};
+;// Child of FilenameEditHandler. Used to detect click events on filename display elements.
+qq.FilenameClickHandler = function(s) {
+ "use strict";
+
+ var inheritedInternalApi = {},
+ spec = {
+ log: function(message, lvl) {},
+ classes: {
+ file: 'qq-upload-file',
+ editNameIcon: 'qq-edit-filename-icon'
+ },
+ onGetUploadStatus: function(fileId) {},
+ onGetName: function(fileId) {}
+ };
+
+ qq.extend(spec, s);
+
+ // This will be called by the parent handler when a `click` event is received on the list element.
+ function examineEvent(target, event) {
+ if (qq(target).hasClass(spec.classes.file) || qq(target).hasClass(spec.classes.editNameIcon)) {
+ var item = inheritedInternalApi.getItemFromEventTarget(target),
+ fileId = inheritedInternalApi.getFileIdFromItem(item),
+ status = spec.onGetUploadStatus(fileId);
+
+ // We only allow users to change filenames of files that have been submitted but not yet uploaded.
+ if (status === qq.status.SUBMITTED) {
+ spec.log(qq.format("Detected valid filename click event on file '{}', ID: {}.", spec.onGetName(fileId), fileId));
+ qq.preventDefault(event);
+
+ inheritedInternalApi.handleFilenameEdit(fileId, target, item, true);
+ }
+ }
+ }
+
+ spec.eventType = 'click';
+ spec.onHandled = examineEvent;
+
+ return qq.extend(this, new qq.FilenameEditHandler(spec, inheritedInternalApi));
+};
+;// Child of FilenameEditHandler. Used to detect focusin events on file edit input elements.
+qq.FilenameInputFocusInHandler = function(s, inheritedInternalApi) {
+ "use strict";
+
+ var spec = {
+ listElement: null,
+ classes: {
+ editFilenameInput: 'qq-edit-filename'
+ },
+ onGetUploadStatus: function(fileId) {},
+ log: function(message, lvl) {}
+ };
+
+ if (!inheritedInternalApi) {
+ inheritedInternalApi = {};
+ }
+
+ // This will be called by the parent handler when a `focusin` event is received on the list element.
+ function handleInputFocus(target, event) {
+ if (qq(target).hasClass(spec.classes.editFilenameInput)) {
+ var item = inheritedInternalApi.getItemFromEventTarget(target),
+ fileId = inheritedInternalApi.getFileIdFromItem(item),
+ status = spec.onGetUploadStatus(fileId);
+
+ if (status === qq.status.SUBMITTED) {
+ spec.log(qq.format("Detected valid filename input focus event on file '{}', ID: {}.", spec.onGetName(fileId), fileId));
+ inheritedInternalApi.handleFilenameEdit(fileId, target, item);
+ }
+ }
+ }
+
+ spec.eventType = 'focusin';
+ spec.onHandled = handleInputFocus;
+
+ qq.extend(spec, s);
+
+ return qq.extend(this, new qq.FilenameEditHandler(spec, inheritedInternalApi));
+};
+;/**
+ * Child of FilenameInputFocusInHandler. Used to detect focus events on file edit input elements. This child module is only
+ * needed for UAs that do not support the focusin event. Currently, only Firefox lacks this event.
+ *
+ * @param spec Overrides for default specifications
+ */
+qq.FilenameInputFocusHandler = function(spec) {
+ "use strict";
+
+ spec.eventType = 'focus';
+ spec.attachTo = null;
+
+ return qq.extend(this, new qq.FilenameInputFocusInHandler(spec, {}));
+};
+
+/*! 2013-07-16 */
diff --git a/ajax/libs/file-uploader/3.7.0/fineuploader.min.css b/ajax/libs/file-uploader/3.7.0/fineuploader.min.css
new file mode 100644
index 000000000..4e66364ab
--- /dev/null
+++ b/ajax/libs/file-uploader/3.7.0/fineuploader.min.css
@@ -0,0 +1,19 @@
+/*!
+ * Fine Uploader
+ *
+ * Copyright 2013, Widen Enterprises, Inc. info@fineuploader.com
+ *
+ * Version: 3.7.0
+ *
+ * Homepage: http://fineuploader.com
+ *
+ * Repository: git://github.com/Widen/fine-uploader.git
+ *
+ * Licensed under GNU GPL v3, see LICENSE
+ */
+
+
+/*! fineuploader 2013-07-16 */
+
+.qq-uploader{position:relative;width:100%}.qq-upload-button{display:block;width:105px;padding:7px 0;text-align:center;background:#800;border-bottom:1px solid #DDD;color:#FFF}.qq-upload-button-hover{background:#C00}.qq-upload-button-focus{outline:1px dotted #000}.qq-upload-drop-area,.qq-upload-extra-drop-area{position:absolute;top:0;left:0;width:100%;height:100%;min-height:30px;z-index:2;background:#FF9797;text-align:center}.qq-upload-drop-area span{display:block;position:absolute;top:50%;width:100%;margin-top:-8px;font-size:16px}.qq-upload-extra-drop-area{position:relative;margin-top:50px;font-size:16px;padding-top:30px;height:20px;min-height:40px}.qq-upload-drop-area-active{background:#FF7171}.qq-upload-list{margin:0;padding:0;list-style:none}.qq-upload-list li{margin:0;padding:9px;line-height:15px;font-size:16px;background-color:#FFF0BD}.qq-upload-file,.qq-upload-spinner,.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-failed-text,.qq-upload-finished,.qq-upload-delete{margin-right:12px}.qq-upload-file{}.qq-upload-spinner{display:inline-block;background:url(loading.gif);width:15px;height:15px;vertical-align:text-bottom}.qq-drop-processing{display:none}.qq-drop-processing-spinner{display:inline-block;background:url(processing.gif);width:24px;height:24px;vertical-align:text-bottom}.qq-upload-finished{display:none;width:15px;height:15px;vertical-align:text-bottom}.qq-upload-retry,.qq-upload-delete{display:none;color:#000}.qq-upload-cancel,.qq-upload-delete{color:#000}.qq-upload-retryable .qq-upload-retry{display:inline}.qq-upload-size,.qq-upload-cancel,.qq-upload-retry,.qq-upload-delete{font-size:12px;font-weight:400}.qq-upload-failed-text{display:none;font-style:italic;font-weight:700}.qq-upload-failed-icon{display:none;width:15px;height:15px;vertical-align:text-bottom}.qq-upload-fail .qq-upload-failed-text{display:inline}.qq-upload-retrying .qq-upload-failed-text{display:inline;color:#D60000}.qq-upload-list li.qq-upload-success{background-color:#5DA30C;color:#FFF}.qq-upload-list li.qq-upload-fail{background-color:#D60000;color:#FFF}.qq-progress-bar{background:-moz-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,rgba(30,87,153,1)),color-stop(50%,rgba(41,137,216,1)),color-stop(51%,rgba(32,124,202,1)),color-stop(100%,rgba(125,185,232,1)));background:-webkit-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-o-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-ms-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:linear-gradient(to bottom,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);width:0;height:15px;border-radius:6px;margin-bottom:3px;display:none}INPUT.qq-edit-filename{position:absolute;opacity:0;filter:alpha(opacity=0);-ms-filter:"alpha(Opacity=0)"}.qq-upload-file.qq-editable{cursor:pointer}.qq-edit-filename-icon.qq-editable{display:inline-block;cursor:pointer}INPUT.qq-edit-filename.qq-editing{position:static;margin-top:-5px;margin-right:10px;margin-bottom:-5px;opacity:1;filter:alpha(opacity=100);-ms-filter:"alpha(Opacity=100)"}.qq-edit-filename-icon{display:none;background:url(edit.gif);width:15px;height:15px;vertical-align:text-bottom;margin-right:5px}INPUT.qq-edit-filename.qq-editing~.qq-upload-cancel{display:none}
+/*! 2013-07-16 */
diff --git a/ajax/libs/file-uploader/3.7.0/fineuploader.min.js b/ajax/libs/file-uploader/3.7.0/fineuploader.min.js
new file mode 100644
index 000000000..57d4c0b20
--- /dev/null
+++ b/ajax/libs/file-uploader/3.7.0/fineuploader.min.js
@@ -0,0 +1,19 @@
+/*!
+ * Fine Uploader
+ *
+ * Copyright 2013, Widen Enterprises, Inc. info@fineuploader.com
+ *
+ * Version: 3.7.0
+ *
+ * Homepage: http://fineuploader.com
+ *
+ * Repository: git://github.com/Widen/fine-uploader.git
+ *
+ * Licensed under GNU GPL v3, see LICENSE
+ */
+
+
+var qq=function(a){"use strict";return{hide:function(){return a.style.display="none",this},attach:function(b,c){return a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent&&a.attachEvent("on"+b,c),function(){qq(a).detach(b,c)}},detach:function(b,c){return a.removeEventListener?a.removeEventListener(b,c,!1):a.attachEvent&&a.detachEvent("on"+b,c),this},contains:function(b){return b?a===b?!0:a.contains?a.contains(b):!!(8&b.compareDocumentPosition(a)):!1},insertBefore:function(b){return b.parentNode.insertBefore(a,b),this},remove:function(){return a.parentNode.removeChild(a),this},css:function(b){return null!=b.opacity&&"string"!=typeof a.style.opacity&&"undefined"!=typeof a.filters&&(b.filter="alpha(opacity="+Math.round(100*b.opacity)+")"),qq.extend(a.style,b),this},hasClass:function(b){var c=new RegExp("(^| )"+b+"( |$)");return c.test(a.className)},addClass:function(b){return qq(a).hasClass(b)||(a.className+=" "+b),this},removeClass:function(b){var c=new RegExp("(^| )"+b+"( |$)");return a.className=a.className.replace(c," ").replace(/^\s+|\s+$/g,""),this},getByClass:function(b){var c,d=[];return a.querySelectorAll?a.querySelectorAll("."+b):(c=a.getElementsByTagName("*"),qq.each(c,function(a,c){qq(c).hasClass(b)&&d.push(c)}),d)},children:function(){for(var b=[],c=a.firstChild;c;)1===c.nodeType&&b.push(c),c=c.nextSibling;return b},setText:function(b){return a.innerText=b,a.textContent=b,this},clearText:function(){return qq(a).setText("")}}};qq.log=function(a,b){"use strict";window.console&&(b&&"info"!==b?window.console[b]?window.console[b](a):window.console.log("<"+b+"> "+a):window.console.log(a))},qq.isObject=function(a){"use strict";return a&&!a.nodeType&&"[object Object]"===Object.prototype.toString.call(a)},qq.isFunction=function(a){"use strict";return"function"==typeof a},qq.isArray=function(a){"use strict";return"[object Array]"===Object.prototype.toString.call(a)},qq.isString=function(a){"use strict";return"[object String]"===Object.prototype.toString.call(a)},qq.trimStr=function(a){return String.prototype.trim?a.trim():a.replace(/^\s+|\s+$/g,"")},qq.format=function(a){"use strict";var b=Array.prototype.slice.call(arguments,1),c=a;return qq.each(b,function(a,b){c=c.replace(/{}/,b)}),c},qq.isFile=function(a){"use strict";return window.File&&"[object File]"===Object.prototype.toString.call(a)},qq.isFileList=function(a){return window.FileList&&"[object FileList]"===Object.prototype.toString.call(a)},qq.isFileOrInput=function(a){"use strict";return qq.isFile(a)||qq.isInput(a)},qq.isInput=function(a){return window.HTMLInputElement&&"[object HTMLInputElement]"===Object.prototype.toString.call(a)&&a.type&&"file"===a.type.toLowerCase()?!0:a.tagName&&"input"===a.tagName.toLowerCase()&&a.type&&"file"===a.type.toLowerCase()?!0:!1},qq.isBlob=function(a){"use strict";return window.Blob&&"[object Blob]"===Object.prototype.toString.call(a)},qq.isXhrUploadSupported=function(){"use strict";var a=document.createElement("input");return a.type="file",void 0!==a.multiple&&"undefined"!=typeof File&&"undefined"!=typeof FormData&&"undefined"!=typeof(new XMLHttpRequest).upload},qq.isFolderDropSupported=function(a){"use strict";return a.items&&a.items[0].webkitGetAsEntry},qq.isFileChunkingSupported=function(){"use strict";return!qq.android()&&qq.isXhrUploadSupported()&&(void 0!==File.prototype.slice||void 0!==File.prototype.webkitSlice||void 0!==File.prototype.mozSlice)},qq.extend=function(a,b,c){"use strict";return qq.each(b,function(b,d){c&&qq.isObject(d)?(void 0===a[b]&&(a[b]={}),qq.extend(a[b],d,!0)):a[b]=d}),a},qq.indexOf=function(a,b,c){"use strict";if(a.indexOf)return a.indexOf(b,c);c=c||0;var d=a.length;for(0>c&&(c+=d);d>c;c+=1)if(a.hasOwnProperty(c)&&a[c]===b)return c;return-1},qq.getUniqueId=function(){"use strict";return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(a){var b=0|16*Math.random(),c="x"==a?b:8|3&b;return c.toString(16)})},qq.ie=function(){"use strict";return-1!==navigator.userAgent.indexOf("MSIE")},qq.ie10=function(){"use strict";return-1!==navigator.userAgent.indexOf("MSIE 10")},qq.safari=function(){"use strict";return void 0!==navigator.vendor&&-1!==navigator.vendor.indexOf("Apple")},qq.chrome=function(){"use strict";return void 0!==navigator.vendor&&-1!==navigator.vendor.indexOf("Google")},qq.firefox=function(){"use strict";return-1!==navigator.userAgent.indexOf("Mozilla")&&void 0!==navigator.vendor&&""===navigator.vendor},qq.windows=function(){"use strict";return"Win32"===navigator.platform},qq.android=function(){"use strict";return-1!==navigator.userAgent.toLowerCase().indexOf("android")},qq.ios=function(){"use strict";return-1!==navigator.userAgent.indexOf("iPad")||-1!==navigator.userAgent.indexOf("iPod")||-1!==navigator.userAgent.indexOf("iPhone")},qq.preventDefault=function(a){"use strict";a.preventDefault?a.preventDefault():a.returnValue=!1},qq.toElement=function(){"use strict";var a=document.createElement("div");return function(b){a.innerHTML=b;var c=a.firstChild;return a.removeChild(c),c}}(),qq.each=function(a,b){"use strict";var c,d;if(a)if(qq.isArray(a))for(c=0;cd;d+=1)h(a[d],d);else if("undefined"!=typeof a&&null!==a&&"object"==typeof a)for(d in a)a.hasOwnProperty(d)&&h(a[d],d);else f.push(encodeURIComponent(b)+"="+encodeURIComponent(a));return b?f.join(g):f.join(g).replace(/^&/,"").replace(/%20/g,"+")},qq.obj2FormData=function(a,b,c){"use strict";return b||(b=new FormData),qq.each(a,function(a,d){a=c?c+"["+a+"]":a,qq.isObject(d)?qq.obj2FormData(d,b,a):qq.isFunction(d)?b.append(a,d()):b.append(a,d)}),b},qq.obj2Inputs=function(a,b){"use strict";var c;return b||(b=document.createElement("form")),qq.obj2FormData(a,{append:function(a,d){c=document.createElement("input"),c.setAttribute("name",a),c.setAttribute("value",d),b.appendChild(c)}}),b},qq.setCookie=function(a,b,c){var d=new Date,e="";c&&(d.setTime(d.getTime()+1e3*60*60*24*c),e="; expires="+d.toGMTString()),document.cookie=a+"="+b+e+"; path=/"},qq.getCookie=function(a){var b,c=a+"=",d=document.cookie.split(";");return qq.each(d,function(a,d){for(var e=d;" "==e.charAt(0);)e=e.substring(1,e.length);return 0===e.indexOf(c)?(b=e.substring(c.length,e.length),!1):void 0}),b},qq.getCookieNames=function(a){var b=document.cookie.split(";"),c=[];return qq.each(b,function(b,d){d=qq.trimStr(d);var e=d.indexOf("=");d.match(a)&&c.push(d.substr(0,e))}),c},qq.deleteCookie=function(a){qq.setCookie(a,"",-1)},qq.areCookiesEnabled=function(){var a=1e5*Math.random(),b="qqCookieTest:"+a;return qq.setCookie(b,1),qq.getCookie(b)?(qq.deleteCookie(b),!0):!1},qq.parseJson=function(json){return window.JSON&&qq.isFunction(JSON.parse)?JSON.parse(json):eval("("+json+")")},qq.DisposeSupport=function(){"use strict";var a=[];return{dispose:function(){var b;do b=a.shift(),b&&b();while(b)},attach:function(){var a=arguments;this.addDisposer(qq(a[0]).attach.apply(this,Array.prototype.slice.call(arguments,1)))},addDisposer:function(b){a.push(b)}}},qq.version="3.7.0",qq.supportedFeatures=function(){function a(){var a,b=!0;try{a=document.createElement("input"),a.type="file",qq(a).hide(),a.disabled&&(b=!1)}catch(c){b=!1}return b}function b(){return qq.chrome()&&void 0!==navigator.userAgent.match(/Chrome\/[2][1-9]|Chrome\/[3-9][0-9]/)}function c(){return qq.chrome()&&void 0!==navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/)}function d(){if(window.XMLHttpRequest){var a=new XMLHttpRequest;return void 0!==a.withCredentials}return!1}function e(){return void 0!==window.XDomainRequest}function f(){return d()?!0:e()}var g,h,i,j,k,l,m,n,o,p;return g=a(),h=g&&qq.isXhrUploadSupported(),i=h&&b(),j=h&&qq.isFileChunkingSupported(),k=h&&j&&qq.areCookiesEnabled(),l=h&&c(),m=g&&(void 0!==window.postMessage||h),o=d(),n=e(),p=f(),{uploading:g,ajaxUploading:h,fileDrop:h,folderDrop:i,chunking:j,resume:k,uploadCustomHeaders:h,uploadNonMultipart:h,itemSizeValidation:h,uploadViaPaste:l,progressBar:h,uploadCors:m,deleteFileCorsXhr:o,deleteFileCorsXdr:n,deleteFileCors:p,canDetermineSize:h}}(),qq.Promise=function(){"use strict";var a,b,c=[],d=[],e=[],f=0;return{then:function(e,g){return 0===f?(e&&c.push(e),g&&d.push(g)):-1===f&&g?g(b):e&&e(a),this},done:function(a){return 0===f?e.push(a):a(),this},success:function(b){return f=1,a=b,c.length&&qq.each(c,function(a,c){c(b)}),e.length&&qq.each(e,function(a,b){b()}),this},failure:function(a){return f=-1,b=a,d.length&&qq.each(d,function(b,c){c(a)}),e.length&&qq.each(e,function(a,b){b()}),this}}},qq.isPromise=function(a){return a&&a.then&&a.done},qq.UploadButton=function(a){"use strict";function b(){var a=document.createElement("input");return e.multiple&&a.setAttribute("multiple","multiple"),e.acceptFiles&&a.setAttribute("accept",e.acceptFiles),a.setAttribute("type","file"),a.setAttribute("name",e.name),qq(a).css({position:"absolute",right:0,top:0,fontFamily:"Arial",fontSize:"118px",margin:0,padding:0,cursor:"pointer",opacity:0}),e.element.appendChild(a),d.attach(a,"change",function(){e.onChange(a)}),d.attach(a,"mouseover",function(){qq(e.element).addClass(e.hoverClass)}),d.attach(a,"mouseout",function(){qq(e.element).removeClass(e.hoverClass)}),d.attach(a,"focus",function(){qq(e.element).addClass(e.focusClass)}),d.attach(a,"blur",function(){qq(e.element).removeClass(e.focusClass)}),window.attachEvent&&a.setAttribute("tabIndex","-1"),a}var c,d=new qq.DisposeSupport,e={element:null,multiple:!1,acceptFiles:null,name:"qqfile",onChange:function(){},hoverClass:"qq-upload-button-hover",focusClass:"qq-upload-button-focus"};return qq.extend(e,a),qq(e.element).css({position:"relative",overflow:"hidden",direction:"ltr"}),c=b(),{getInput:function(){return c},reset:function(){c.parentNode&&qq(c).remove(),qq(e.element).removeClass(e.focusClass),c=b()}}},qq.PasteSupport=function(a){"use strict";function b(a){return a.type&&0===a.type.indexOf("image/")}function c(){qq(e.targetElement).attach("paste",function(a){var c=a.clipboardData;c&&qq.each(c.items,function(a,c){if(b(c)){var d=c.getAsFile();e.callbacks.pasteReceived(d)}})})}function d(){f&&f()}var e,f;return e={targetElement:null,callbacks:{log:function(){},pasteReceived:function(){}}},qq.extend(e,a),c(),{reset:function(){d()}}},qq.UploadData=function(a){function b(a){if(qq.isArray(a)){var b=[];return qq.each(a,function(a,c){b.push(f[g[c]])}),b}return f[g[a]]}function c(a){if(qq.isArray(a)){var b=[];return qq.each(a,function(a,c){b.push(f[h[c]])}),b}return f[h[a]]}function d(a){var b=[],c=[].concat(a);return qq.each(c,function(a,c){var d=i[c];void 0!==d&&qq.each(d,function(a,c){b.push(f[c])})}),b}var e,f=[],g={},h={},i={};return e={added:function(b){var c=a.getUuid(b),d=a.getName(b),e=a.getSize(b),j=qq.status.SUBMITTING,k=f.push({id:b,name:d,originalName:d,uuid:c,size:e,status:j})-1;g[b]=k,h[c]=k,void 0===i[j]&&(i[j]=[]),i[j].push(k),a.onStatusChange(b,void 0,j)},retrieve:function(a){return qq.isObject(a)&&f.length?void 0!==a.id?b(a.id):void 0!==a.uuid?c(a.uuid):a.status?d(a.status):void 0:qq.extend([],f,!0)},reset:function(){f=[],g={},h={},i={}},setStatus:function(b,c){var d=g[b],e=f[d].status,h=qq.indexOf(i[e],d);i[e].splice(h,1),f[d].status=c,void 0===i[c]&&(i[c]=[]),i[c].push(d),a.onStatusChange(b,e,c)},uuidChanged:function(a,b){var c=g[a],d=f[c].uuid;f[c].uuid=b,h[b]=c,delete h[d]},nameChanged:function(a,b){var c=g[a];f[c].name=b}}},qq.status={SUBMITTING:"submitting",SUBMITTED:"submitted",REJECTED:"rejected",QUEUED:"queued",CANCELED:"canceled",UPLOADING:"uploading",UPLOAD_RETRYING:"retrying upload",UPLOAD_SUCCESSFUL:"upload successful",UPLOAD_FAILED:"upload failed",DELETE_FAILED:"delete failed",DELETING:"deleting",DELETED:"deleted"},qq.FineUploaderBasic=function(a){this._options={debug:!1,button:null,multiple:!0,maxConnections:3,disableCancelForFormUploads:!1,autoUpload:!0,request:{endpoint:"/server/upload",params:{},paramsInBody:!0,customHeaders:{},forceMultipart:!0,inputName:"qqfile",uuidName:"qquuid",totalFileSizeName:"qqtotalfilesize",filenameParam:"qqfilename"},validation:{allowedExtensions:[],sizeLimit:0,minSizeLimit:0,itemLimit:0,stopOnFirstInvalidFile:!0,acceptFiles:null},callbacks:{onSubmit:function(){},onSubmitted:function(){},onComplete:function(){},onCancel:function(){},onUpload:function(){},onUploadChunk:function(){},onResume:function(){},onProgress:function(){},onError:function(){},onAutoRetry:function(){},onManualRetry:function(){},onValidateBatch:function(){},onValidate:function(){},onSubmitDelete:function(){},onDelete:function(){},onDeleteComplete:function(){},onPasteReceived:function(){},onStatusChange:function(){}},messages:{typeError:"{file} has an invalid extension. Valid extension(s): {extensions}.",sizeError:"{file} is too large, maximum file size is {sizeLimit}.",minSizeError:"{file} is too small, minimum file size is {minSizeLimit}.",emptyError:"{file} is empty, please select files again without it.",noFilesError:"No files to upload.",tooManyItemsError:"Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.",retryFailTooManyItems:"Retry failed - you have reached your file limit.",onLeave:"The files are being uploaded, if you leave now the upload will be cancelled."},retry:{enableAuto:!1,maxAutoAttempts:3,autoAttemptDelay:5,preventRetryResponseProperty:"preventRetry"},classes:{buttonHover:"qq-upload-button-hover",buttonFocus:"qq-upload-button-focus"},chunking:{enabled:!1,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalFileSize:"qqtotalfilesize",totalParts:"qqtotalparts"}},resume:{enabled:!1,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},formatFileName:function(a){return void 0!==a&&a.length>33&&(a=a.slice(0,19)+"..."+a.slice(-14)),a},text:{defaultResponseError:"Upload failure reason unknown",sizeSymbols:["kB","MB","GB","TB","PB","EB"]},deleteFile:{enabled:!1,method:"DELETE",endpoint:"/server/upload",customHeaders:{},params:{}},cors:{expected:!1,sendCredentials:!1,allowXdr:!1},blobs:{defaultName:"misc_data"},paste:{targetElement:null,defaultName:"pasted_image"},camera:{ios:!1}},qq.extend(this._options,a,!0),this._handleCameraAccess(),this._wrapCallbacks(),this._disposeSupport=new qq.DisposeSupport,this._filesInProgress=[],this._storedIds=[],this._autoRetries=[],this._retryTimeouts=[],this._preventRetries=[],this._netUploadedOrQueued=0,this._netUploaded=0,this._uploadData=this._createUploadDataTracker(),this._paramsStore=this._createParamsStore("request"),this._deleteFileParamsStore=this._createParamsStore("deleteFile"),this._endpointStore=this._createEndpointStore("request"),this._deleteFileEndpointStore=this._createEndpointStore("deleteFile"),this._handler=this._createUploadHandler(),this._deleteHandler=this._createDeleteHandler(),this._options.button&&(this._button=this._createUploadButton(this._options.button)),this._options.paste.targetElement&&(this._pasteHandler=this._createPasteHandler()),this._preventLeaveInProgress()},qq.FineUploaderBasic.prototype={log:function(a,b){!this._options.debug||b&&"info"!==b?b&&"info"!==b&&qq.log("[FineUploader "+qq.version+"] "+a,b):qq.log("[FineUploader "+qq.version+"] "+a)},setParams:function(a,b){null==b?this._options.request.params=a:this._paramsStore.setParams(a,b)},setDeleteFileParams:function(a,b){null==b?this._options.deleteFile.params=a:this._deleteFileParamsStore.setParams(a,b)},setEndpoint:function(a,b){null==b?this._options.request.endpoint=a:this._endpointStore.setEndpoint(a,b)},getInProgress:function(){return this._filesInProgress.length},getNetUploads:function(){return this._netUploaded},uploadStoredFiles:function(){var a;if(0===this._storedIds.length)this._itemError("noFilesError");else for(;this._storedIds.length;)a=this._storedIds.shift(),this._filesInProgress.push(a),this._handler.upload(a)},clearStoredFiles:function(){this._storedIds=[]},retry:function(a){return this._onBeforeManualRetry(a)?(this._netUploadedOrQueued++,this._uploadData.setStatus(a,qq.status.UPLOAD_RETRYING),this._handler.retry(a),!0):!1},cancel:function(a){this._handler.cancel(a)},cancelAll:function(){var a=[],b=this;qq.extend(a,this._storedIds),qq.each(a,function(a,c){b.cancel(c)}),this._handler.cancelAll()},reset:function(){this.log("Resetting uploader..."),this._handler.reset(),this._filesInProgress=[],this._storedIds=[],this._autoRetries=[],this._retryTimeouts=[],this._preventRetries=[],this._button.reset(),this._paramsStore.reset(),this._endpointStore.reset(),this._netUploadedOrQueued=0,this._netUploaded=0,this._uploadData.reset(),this._pasteHandler&&this._pasteHandler.reset()},addFiles:function(a,b,c){var d,e,f,g=this,h=[];if(a){for(qq.isFileList(a)||(a=[].concat(a)),d=0;d=0&&this._storedIds.splice(b,1),this._uploadData.setStatus(a,qq.status.CANCELED)},_isDeletePossible:function(){return this._options.deleteFile.enabled?this._options.cors.expected?qq.supportedFeatures.deleteFileCorsXhr?!0:qq.supportedFeatures.deleteFileCorsXdr&&this._options.cors.allowXdr?!0:!1:!0:!1},_onSubmitDelete:function(a,b){return this._isDeletePossible()?this._handleCheckedCallback({name:"onSubmitDelete",callback:qq.bind(this._options.callbacks.onSubmitDelete,this,a),onSuccess:b||qq.bind(this._deleteHandler.sendDelete,this,a,this.getUuid(a)),identifier:a}):(this.log("Delete request ignored for ID "+a+", delete feature is disabled or request not possible "+"due to CORS on a user agent that does not support pre-flighting.","warn"),!1)},_onDelete:function(a){this._uploadData.setStatus(a,qq.status.DELETING)},_onDeleteComplete:function(a,b,c){var d=this._handler.getName(a);c?(this._uploadData.setStatus(a,qq.status.DELETE_FAILED),this.log("Delete request for '"+d+"' has failed.","error"),void 0===b.withCredentials?this._options.callbacks.onError(a,d,"Delete request failed",b):this._options.callbacks.onError(a,d,"Delete request failed with response code "+b.status,b)):(this._netUploadedOrQueued--,this._netUploaded--,this._handler.expunge(a),this._uploadData.setStatus(a,qq.status.DELETED),this.log("Delete request for '"+d+"' has succeeded."))},_removeFromFilesInProgress:function(a){var b=qq.indexOf(this._filesInProgress,a);b>=0&&this._filesInProgress.splice(b,1)},_onUpload:function(a){this._uploadData.setStatus(a,qq.status.UPLOADING)},_onInputChange:function(a){qq.supportedFeatures.ajaxUploading?this.addFiles(a.files):this.addFiles(a),this._button.reset()},_onBeforeAutoRetry:function(a,b){this.log("Waiting "+this._options.retry.autoAttemptDelay+" seconds before retrying "+b+"...")},_onAutoRetry:function(a,b){this.log("Retrying "+b+"..."),this._autoRetries[a]++,this._uploadData.setStatus(a,qq.status.UPLOAD_RETRYING),this._handler.retry(a)},_shouldAutoRetry:function(a){return!this._preventRetries[a]&&this._options.retry.enableAuto?(void 0===this._autoRetries[a]&&(this._autoRetries[a]=0),this._autoRetries[a]0&&this._netUploadedOrQueued+1>b?(this._itemError("retryFailTooManyItems"),!1):(this.log("Retrying upload for '"+c+"' (id: "+a+")..."),this._filesInProgress.push(a),!0)}return this.log("'"+a+"' is not a valid file ID","error"),!1},_maybeParseAndSendUploadError:function(a,b,c,d){if(!c.success)if(d&&200!==d.status&&!c.error)this._options.callbacks.onError(a,b,"XHR returned response code "+d.status,d);else{var e=c.error?c.error:this._options.text.defaultResponseError;this._options.callbacks.onError(a,b,e,d)}},_prepareItemsForUpload:function(a,b,c){var d=this._getValidationDescriptors(a);this._handleCheckedCallback({name:"onValidateBatch",callback:qq.bind(this._options.callbacks.onValidateBatch,this,d),onSuccess:qq.bind(this._onValidateBatchCallbackSuccess,this,d,a,b,c),identifier:"batch validation"})},_upload:function(a,b,c){var d=this._handler.add(a),e=this._handler.getName(d);this._uploadData.added(d),b&&this.setParams(b,d),c&&this.setEndpoint(c,d),this._handleCheckedCallback({name:"onSubmit",callback:qq.bind(this._options.callbacks.onSubmit,this,d,e),onSuccess:qq.bind(this._onSubmitCallbackSuccess,this,d,e),onFailure:qq.bind(this._fileOrBlobRejected,this,d,e),identifier:d})},_onSubmitCallbackSuccess:function(a){this._uploadData.setStatus(a,qq.status.SUBMITTED),this._onSubmit.apply(this,arguments),this._onSubmitted.apply(this,arguments),this._options.callbacks.onSubmitted.apply(this,arguments),this._options.autoUpload?this._handler.upload(a)||this._uploadData.setStatus(a,qq.status.QUEUED):this._storeForLater(a)},_onSubmitted:function(){},_storeForLater:function(a){this._storedIds.push(a)},_onValidateBatchCallbackSuccess:function(a,b,c,d){var e,f=this._options.validation.itemLimit,g=this._netUploadedOrQueued+a.length;0===f||f>=g?b.length>0?this._handleCheckedCallback({name:"onValidate",callback:qq.bind(this._options.callbacks.onValidate,this,b[0]),onSuccess:qq.bind(this._onValidateCallbackSuccess,this,b,0,c,d),onFailure:qq.bind(this._onValidateCallbackFailure,this,b,0,c,d),identifier:"Item '"+b[0].name+"', size: "+b[0].size}):this._itemError("noFilesError"):(e=this._options.messages.tooManyItemsError.replace(/\{netItems\}/g,g).replace(/\{itemLimit\}/g,f),this._batchError(e))},_onValidateCallbackSuccess:function(a,b,c,d){var e=b+1,f=this._getValidationDescriptor(a[b]),g=!1;this._validateFileOrBlobData(a[b],f)&&(g=!0,this._upload(a[b],c,d)),this._maybeProcessNextItemAfterOnValidateCallback(g,a,e,c,d)},_onValidateCallbackFailure:function(a,b,c,d){var e=b+1;this._fileOrBlobRejected(void 0,a[0].name),this._maybeProcessNextItemAfterOnValidateCallback(!1,a,e,c,d)},_maybeProcessNextItemAfterOnValidateCallback:function(a,b,c,d,e){var f=this;b.length>c&&(a||!this._options.validation.stopOnFirstInvalidFile)&&setTimeout(function(){var a=f._getValidationDescriptor(b[c]);f._handleCheckedCallback({name:"onValidate",callback:qq.bind(f._options.callbacks.onValidate,f,b[c]),onSuccess:qq.bind(f._onValidateCallbackSuccess,f,b,c,d,e),onFailure:qq.bind(f._onValidateCallbackFailure,f,b,c,d,e),identifier:"Item '"+a.name+"', size: "+a.size})},0)},_validateFileOrBlobData:function(a,b){var c=b.name,d=b.size,e=!0;return this._options.callbacks.onValidate(b)===!1&&(e=!1),qq.isFileOrInput(a)&&!this._isAllowedExtension(c)?(this._itemError("typeError",c),e=!1):0===d?(this._itemError("emptyError",c),e=!1):d&&this._options.validation.sizeLimit&&d>this._options.validation.sizeLimit?(this._itemError("sizeError",c),e=!1):d&&d999);return Math.max(a,.1).toFixed(1)+this._options.text.sizeSymbols[b]},_wrapCallbacks:function(){var a,b;a=this,b=function(b,c,d){try{return c.apply(a,d)}catch(e){a.log("Caught exception in '"+b+"' callback - "+e.message,"error")}};for(var c in this._options.callbacks)!function(){var d,e;d=c,e=a._options.callbacks[d],a._options.callbacks[d]=function(){return b(d,e,arguments)}}()},_parseFileOrBlobDataName:function(a){var b;return b=qq.isFileOrInput(a)?a.value?a.value.replace(/.*(\/|\\)/,""):null!==a.fileName&&void 0!==a.fileName?a.fileName:a.name:a.name},_parseFileOrBlobDataSize:function(a){var b;return qq.isFileOrInput(a)?a.value||(b=null!==a.fileSize&&void 0!==a.fileSize?a.fileSize:a.size):b=a.blob.size,b},_getValidationDescriptor:function(a){var b,c,d;return d={},b=this._parseFileOrBlobDataName(a),c=this._parseFileOrBlobDataSize(a),d.name=b,void 0!==c&&(d.size=c),d},_getValidationDescriptors:function(a){var b=this,c=[];return qq.each(a,function(a,d){c.push(b._getValidationDescriptor(d))
+}),c},_createParamsStore:function(a){var b={},c=this;return{setParams:function(a,c){var d={};qq.extend(d,a),b[c]=d},getParams:function(d){var e={};return null!=d&&b[d]?qq.extend(e,b[d]):qq.extend(e,c._options[a].params),e},remove:function(a){return delete b[a]},reset:function(){b={}}}},_createEndpointStore:function(a){var b={},c=this;return{setEndpoint:function(a,c){b[c]=a},getEndpoint:function(d){return null!=d&&b[d]?b[d]:c._options[a].endpoint},remove:function(a){return delete b[a]},reset:function(){b={}}}},_handleCameraAccess:function(){this._options.camera.ios&&qq.ios()&&(this._options.multiple=!1,null===this._options.validation.acceptFiles?this._options.validation.acceptFiles="image/*;capture=camera":this._options.validation.acceptFiles+=",image/*;capture=camera")}},qq.DragAndDrop=function(a){"use strict";function b(a){h.callbacks.dropLog("Grabbed "+a.length+" dropped files."),i.dropDisabled(!1),h.callbacks.processingDroppedFilesComplete(a)}function c(a){var b,d,e=new qq.Promise;return a.isFile?a.file(function(a){j.push(a),e.success()},function(b){h.callbacks.dropLog("Problem parsing '"+a.fullPath+"'. FileError code "+b.code+".","error"),e.failure()}):a.isDirectory&&(b=a.createReader(),b.readEntries(function(a){var b=a.length;for(d=0;d1&&!h.allowMultipleItems)h.callbacks.processingDroppedFilesComplete([]),h.callbacks.dropError("tooManyFilesError",""),i.dropDisabled(!1),g.failure();else{if(j=[],qq.isFolderDropSupported(a))for(d=a.items,b=0;b'+(this._options.dragAndDrop&&this._options.dragAndDrop.disableDefaultDropzone?"":'{dragZoneText}
')+(this._options.button?"":'')+'{dropProcessingText} '+(this._options.listElement?"":'')+"",fileTemplate:'
'+(this._options.editFilename&&this._options.editFilename.enabled?' ':"")+' '+(this._options.editFilename&&this._options.editFilename.enabled?' ':"")+' '+'{cancelButtonText} '+'{retryButtonText} '+'{deleteButtonText} '+'{statusText} '+" ",classes:{button:"qq-upload-button",drop:"qq-upload-drop-area",dropActive:"qq-upload-drop-area-active",list:"qq-upload-list",progressBar:"qq-progress-bar",file:"qq-upload-file",spinner:"qq-upload-spinner",finished:"qq-upload-finished",retrying:"qq-upload-retrying",retryable:"qq-upload-retryable",size:"qq-upload-size",cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry",statusText:"qq-upload-status-text",editFilenameInput:"qq-edit-filename",success:"qq-upload-success",fail:"qq-upload-fail",successIcon:null,failIcon:null,editNameIcon:"qq-edit-filename-icon",editable:"qq-editable",dropProcessing:"qq-drop-processing",dropProcessingSpinner:"qq-drop-processing-spinner"},failedUploadTextDisplay:{mode:"default",maxChars:50,responseProperty:"error",enableTooltip:!0},messages:{tooManyFilesError:"You may only drop one file",unsupportedBrowser:"Unrecoverable error - this browser does not permit file uploading of any kind."},retry:{showAutoRetryNote:!0,autoRetryNote:"Retrying {retryNum}/{maxAuto}...",showButton:!1},deleteFile:{forceConfirm:!1,confirmMessage:"Are you sure you want to delete {filename}?",deletingStatusText:"Deleting...",deletingFailedText:"Delete failed"},display:{fileSizeOnSubmit:!1,prependFiles:!1},paste:{promptForName:!1,namePromptMessage:"Please name this image"},editFilename:{enabled:!1},showMessage:function(a){setTimeout(function(){window.alert(a)},0)},showConfirm:function(a,b,c){setTimeout(function(){var d=window.confirm(a);d?b():c&&c()},0)},showPrompt:function(a,b){var c=new qq.Promise,d=window.prompt(a,b);return null!=d&&qq.trimStr(d).length>0?c.success(d):c.failure("Undefined or invalid user-supplied value."),c}},!0),qq.extend(this._options,a,!0),!qq.supportedFeatures.uploading||this._options.cors.expected&&!qq.supportedFeatures.uploadCors?this._options.element.innerHTML=""+this._options.messages.unsupportedBrowser+"
":(this._wrapCallbacks(),this._options.template=this._options.template.replace(/\{dragZoneText\}/g,this._options.text.dragZone),this._options.template=this._options.template.replace(/\{uploadButtonText\}/g,this._options.text.uploadButton),this._options.template=this._options.template.replace(/\{dropProcessingText\}/g,this._options.text.dropProcessing),this._options.fileTemplate=this._options.fileTemplate.replace(/\{cancelButtonText\}/g,this._options.text.cancelButton),this._options.fileTemplate=this._options.fileTemplate.replace(/\{retryButtonText\}/g,this._options.text.retryButton),this._options.fileTemplate=this._options.fileTemplate.replace(/\{deleteButtonText\}/g,this._options.text.deleteButton),this._options.fileTemplate=this._options.fileTemplate.replace(/\{statusText\}/g,""),this._element=this._options.element,this._element.innerHTML=this._options.template,this._listElement=this._options.listElement||this._find(this._element,"list"),this._classes=this._options.classes,this._button||(this._button=this._createUploadButton(this._find(this._element,"button"))),this._deleteRetryOrCancelClickHandler=this._bindDeleteRetryOrCancelClickEvent(),this._focusinEventSupported=!qq.firefox(),this._isEditFilenameEnabled()&&(this._filenameClickHandler=this._bindFilenameClickEvent(),this._filenameInputFocusInHandler=this._bindFilenameInputFocusInEvent(),this._filenameInputFocusHandler=this._bindFilenameInputFocusEvent()),this._dnd=this._setupDragAndDrop(),this._options.paste.targetElement&&this._options.paste.promptForName&&this._setupPastePrompt(),this._totalFilesInBatch=0,this._filesInBatchAddedToUi=0)},qq.extend(qq.FineUploader.prototype,qq.FineUploaderBasic.prototype),qq.extend(qq.FineUploader.prototype,{clearStoredFiles:function(){qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this,arguments),this._listElement.innerHTML=""},addExtraDropzone:function(a){this._dnd.setupExtraDropzone(a)},removeExtraDropzone:function(a){return this._dnd.removeDropzone(a)},getItemByFileId:function(a){for(var b=this._listElement.firstChild;b;){if(b.qqFileId==a)return b;b=b.nextSibling}},reset:function(){qq.FineUploaderBasic.prototype.reset.apply(this,arguments),this._element.innerHTML=this._options.template,this._listElement=this._options.listElement||this._find(this._element,"list"),this._options.button||(this._button=this._createUploadButton(this._find(this._element,"button"))),this._dnd.dispose(),this._dnd=this._setupDragAndDrop(),this._totalFilesInBatch=0,this._filesInBatchAddedToUi=0},_removeFileItem:function(a){var b=this.getItemByFileId(a);qq(b).remove()},_setupDragAndDrop:function(){var a,b=this,c=this._find(this._element,"dropProcessing"),d=this._options.dragAndDrop.extraDropzones;return a=function(a){a.preventDefault()},this._options.dragAndDrop.disableDefaultDropzone||d.push(this._find(this._options.element,"drop")),new qq.DragAndDrop({dropZoneElements:d,hideDropZonesBeforeEnter:this._options.dragAndDrop.hideDropzones,allowMultipleItems:this._options.multiple,classes:{dropActive:this._options.classes.dropActive},callbacks:{processingDroppedFiles:function(){var d=b._button.getInput();qq(c).css({display:"block"}),qq(d).attach("click",a)},processingDroppedFilesComplete:function(d){var e=b._button.getInput();qq(c).hide(),qq(e).detach("click",a),d&&b.addFiles(d)},dropError:function(a,c){b._itemError(a,c)},dropLog:function(a,c){b.log(a,c)}}})},_bindDeleteRetryOrCancelClickEvent:function(){var a=this;return new qq.DeleteRetryOrCancelClickHandler({listElement:this._listElement,classes:this._classes,log:function(b,c){a.log(b,c)},onDeleteFile:function(b){a.deleteFile(b)},onCancel:function(b){a.cancel(b)},onRetry:function(b){var c=a.getItemByFileId(b);qq(c).removeClass(a._classes.retryable),a.retry(b)},onGetName:function(b){return a.getName(b)}})},_isEditFilenameEnabled:function(){return this._options.editFilename.enabled&&!this._options.autoUpload},_filenameEditHandler:function(){var a=this;return{listElement:this._listElement,classes:this._classes,log:function(b,c){a.log(b,c)},onGetUploadStatus:function(b){return a.getUploads({id:b}).status},onGetName:function(b){return a.getName(b)},onSetName:function(b,c){var d=a.getItemByFileId(b),e=qq(a._find(d,"file")),f=a._options.formatFileName(c);e.setText(f),a.setName(b,c)},onGetInput:function(b){return a._find(b,"editFilenameInput")},onEditingStatusChange:function(b,c){var d=a.getItemByFileId(b),e=qq(a._find(d,"editFilenameInput")),f=qq(a._find(d,"file")),g=qq(a._find(d,"editNameIcon")),h=a._classes.editable;c?(e.addClass("qq-editing"),f.hide(),g.removeClass(h)):(e.removeClass("qq-editing"),f.css({display:""}),g.addClass(h)),qq(d).addClass("qq-temp").removeClass("qq-temp")}}},_onUploadStatusChange:function(a,b,c){if(this._isEditFilenameEnabled()){var d,e,f=this.getItemByFileId(a),g=this._classes.editable;f&&c!==qq.status.SUBMITTED&&(d=qq(this._find(f,"file")),e=qq(this._find(f,"editNameIcon")),d.removeClass(g),e.removeClass(g))}},_bindFilenameInputFocusInEvent:function(){var a=qq.extend({},this._filenameEditHandler());return new qq.FilenameInputFocusInHandler(a)},_bindFilenameInputFocusEvent:function(){var a=qq.extend({},this._filenameEditHandler());return new qq.FilenameInputFocusHandler(a)},_bindFilenameClickEvent:function(){var a=qq.extend({},this._filenameEditHandler());return new qq.FilenameClickHandler(a)},_leaving_document_out:function(a){return(qq.chrome()||qq.safari()&&qq.windows())&&0==a.clientX&&0==a.clientY||qq.firefox()&&!a.relatedTarget},_storeForLater:function(a){qq.FineUploaderBasic.prototype._storeForLater.apply(this,arguments);var b=this.getItemByFileId(a);qq(this._find(b,"spinner")).hide()},_find:function(a,b){var c=qq(a).getByClass(this._options.classes[b])[0];if(!c)throw new Error("element not found "+b);return c},_onSubmit:function(a,b){qq.FineUploaderBasic.prototype._onSubmit.apply(this,arguments),this._addToList(a,b)},_onSubmitted:function(a){if(this._isEditFilenameEnabled()){var b=this.getItemByFileId(a),c=qq(this._find(b,"file")),d=qq(this._find(b,"editNameIcon")),e=this._classes.editable;c.addClass(e),d.addClass(e),this._focusinEventSupported||this._filenameInputFocusHandler.addHandler(this._find(b,"editFilenameInput"))}},_onProgress:function(a,b,c,d){qq.FineUploaderBasic.prototype._onProgress.apply(this,arguments);var e,f,g,h;e=this.getItemByFileId(a),f=this._find(e,"progressBar"),g=Math.round(100*(c/d)),c===d?(h=this._find(e,"cancel"),qq(h).hide(),qq(f).hide(),qq(this._find(e,"statusText")).setText(this._options.text.waitingForResponse),this._displayFileSize(a)):(this._displayFileSize(a,c,d),qq(f).css({display:"block"})),qq(f).css({width:g+"%"})},_onComplete:function(a,b,c){qq.FineUploaderBasic.prototype._onComplete.apply(this,arguments);var d=this.getItemByFileId(a);qq(this._find(d,"statusText")).clearText(),qq(d).removeClass(this._classes.retrying),qq(this._find(d,"progressBar")).hide(),(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading)&&qq(this._find(d,"cancel")).hide(),qq(this._find(d,"spinner")).hide(),c.success?(this._isDeletePossible()&&this._showDeleteLink(a),qq(d).addClass(this._classes.success),this._classes.successIcon&&(this._find(d,"finished").style.display="inline-block",qq(d).addClass(this._classes.successIcon))):(qq(d).addClass(this._classes.fail),this._classes.failIcon&&(this._find(d,"finished").style.display="inline-block",qq(d).addClass(this._classes.failIcon)),this._options.retry.showButton&&!this._preventRetries[a]&&qq(d).addClass(this._classes.retryable),this._controlFailureTextDisplay(d,c))},_onUpload:function(a){qq.FineUploaderBasic.prototype._onUpload.apply(this,arguments),this._showSpinner(a)},_onCancel:function(a){qq.FineUploaderBasic.prototype._onCancel.apply(this,arguments),this._removeFileItem(a)},_onBeforeAutoRetry:function(a){var b,c,d,e,f,g;qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this,arguments),b=this.getItemByFileId(a),c=this._find(b,"progressBar"),this._showCancelLink(b),c.style.width=0,qq(c).hide(),this._options.retry.showAutoRetryNote&&(d=this._find(b,"statusText"),e=this._autoRetries[a]+1,f=this._options.retry.maxAutoAttempts,g=this._options.retry.autoRetryNote.replace(/\{retryNum\}/g,e),g=g.replace(/\{maxAuto\}/g,f),qq(d).setText(g),1===e&&qq(b).addClass(this._classes.retrying))},_onBeforeManualRetry:function(a){var b=this.getItemByFileId(a);return qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this,arguments)?(this._find(b,"progressBar").style.width=0,qq(b).removeClass(this._classes.fail),qq(this._find(b,"statusText")).clearText(),this._showSpinner(a),this._showCancelLink(b),!0):(qq(b).addClass(this._classes.retryable),!1)},_onSubmitDelete:function(a){var b=qq.bind(this._onSubmitDeleteSuccess,this,a);qq.FineUploaderBasic.prototype._onSubmitDelete.call(this,a,b)},_onSubmitDeleteSuccess:function(a){this._options.deleteFile.forceConfirm?this._showDeleteConfirm(a):this._sendDeleteRequest(a)},_onDeleteComplete:function(a,b,c){qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this,arguments);var d=this.getItemByFileId(a),e=this._find(d,"spinner"),f=this._find(d,"statusText");qq(e).hide(),c?(qq(f).setText(this._options.deleteFile.deletingFailedText),this._showDeleteLink(a)):this._removeFileItem(a)},_sendDeleteRequest:function(a){var b=this.getItemByFileId(a),c=this._find(b,"deleteButton"),d=this._find(b,"statusText");qq(c).hide(),this._showSpinner(a),qq(d).setText(this._options.deleteFile.deletingStatusText),this._deleteHandler.sendDelete(a,this.getUuid(a))},_showDeleteConfirm:function(a){var b=this._handler.getName(a),c=this._options.deleteFile.confirmMessage.replace(/\{filename\}/g,b),d=(this.getUuid(a),this);this._options.showConfirm(c,function(){d._sendDeleteRequest(a)})},_addToList:function(a,b){var c=qq.toElement(this._options.fileTemplate);if(this._options.disableCancelForFormUploads&&!qq.supportedFeatures.ajaxUploading){var d=this._find(c,"cancel");qq(d).remove()}c.qqFileId=a;var e=this._find(c,"file");qq(e).setText(this._options.formatFileName(b)),qq(this._find(c,"size")).hide(),this._options.multiple||(this._handler.cancelAll(),this._clearList()),this._options.display.prependFiles?this._prependItem(c):this._listElement.appendChild(c),this._filesInBatchAddedToUi+=1,this._options.display.fileSizeOnSubmit&&qq.supportedFeatures.ajaxUploading&&this._displayFileSize(a)},_prependItem:function(a){var b=this._listElement,c=b.firstChild;this._totalFilesInBatch>1&&this._filesInBatchAddedToUi>0&&(c=qq(b).children()[this._filesInBatchAddedToUi-1].nextSibling),b.insertBefore(a,c)},_clearList:function(){this._listElement.innerHTML="",this.clearStoredFiles()},_displayFileSize:function(a,b,c){var d=this.getItemByFileId(a),e=this.getSize(a),f=this._formatSize(e),g=this._find(d,"size");void 0!==b&&void 0!==c&&(f=this._formatProgress(b,c)),qq(g).css({display:"inline"}),qq(g).setText(f)},_formatProgress:function(a,b){function c(a,b){d=d.replace(a,b)}var d=this._options.text.formatProgress;return c("{percent}",Math.round(100*(a/b))),c("{total_size}",this._formatSize(b)),d},_controlFailureTextDisplay:function(a,b){var c,d,e,f,g;c=this._options.failedUploadTextDisplay.mode,d=this._options.failedUploadTextDisplay.maxChars,e=this._options.failedUploadTextDisplay.responseProperty,"custom"===c?(f=b[e],f?f.length>d&&(g=f.substring(0,d)+"..."):(f=this._options.text.failUpload,this.log("'"+e+"' is not a valid property on the server response.","warn")),qq(this._find(a,"statusText")).setText(g||f),this._options.failedUploadTextDisplay.enableTooltip&&this._showTooltip(a,f)):"default"===c?qq(this._find(a,"statusText")).setText(this._options.text.failUpload):"none"!==c&&this.log("failedUploadTextDisplay.mode value of '"+c+"' is not valid","warn")},_showTooltip:function(a,b){a.title=b},_showSpinner:function(a){var b=this.getItemByFileId(a),c=this._find(b,"spinner");c.style.display="inline-block"},_showCancelLink:function(a){if(!this._options.disableCancelForFormUploads||qq.supportedFeatures.ajaxUploading){var b=this._find(a,"cancel");qq(b).css({display:"inline"})}},_showDeleteLink:function(a){var b=this.getItemByFileId(a),c=this._find(b,"deleteButton");qq(c).css({display:"inline"})},_itemError:function(){var a=qq.FineUploaderBasic.prototype._itemError.apply(this,arguments);this._options.showMessage(a)},_batchError:function(a){qq.FineUploaderBasic.prototype._batchError.apply(this,arguments),this._options.showMessage(a)},_setupPastePrompt:function(){var a=this;this._options.callbacks.onPasteReceived=function(){var b=a._options.paste.namePromptMessage,c=a._options.paste.defaultName;return a._options.showPrompt(b,c)}},_fileOrBlobRejected:function(){this._totalFilesInBatch-=1,qq.FineUploaderBasic.prototype._fileOrBlobRejected.apply(this,arguments)},_prepareItemsForUpload:function(a){this._totalFilesInBatch=a.length,this._filesInBatchAddedToUi=0,qq.FineUploaderBasic.prototype._prepareItemsForUpload.apply(this,arguments)}}),qq.AjaxRequestor=function(a){"use strict";function b(){return qq.indexOf(["GET","POST","HEAD"],v.method)>=0}function c(){var a=!1;return qq.each(a,function(b,c){return qq.indexOf(["Accept","Accept-Language","Content-Language","Content-Type"],c)<0?(a=!0,!1):void 0}),a}function d(a){return v.cors.expected&&void 0===a.withCredentials}function e(){var a;return window.XMLHttpRequest&&(a=new XMLHttpRequest,void 0===a.withCredentials&&(a=new XDomainRequest)),a}function f(a,b){var c=u[a].xhr;return c||b||(c=v.cors.expected?e():new XMLHttpRequest,u[a].xhr=c),c}function g(a){var b,c=qq.indexOf(t,a),d=v.maxConnections;delete u[a],t.splice(c,1),t.length>=d&&d>c&&(b=t[d-1],j(b))}function h(a,b){var c=f(a),e=v.method,h=b===!1;g(a),h?r(e+" request for "+a+" has failed","error"):d(c)||q(c.status)||(h=!0,r(e+" request for "+a+" has failed - response code "+c.status,"error")),v.onComplete(a,c,h)}function i(a){var b={},c=u[a].additionalParams,d=v.mandatedParams;return v.paramsStore.getParams&&(b=v.paramsStore.getParams(a)),c&&qq.each(c,function(a,c){b[a]=c}),d&&qq.each(d,function(a,c){b[a]=c}),b}function j(a){var b,c=f(a),e=v.method,g=i(a);v.onSend(a),b=k(a,g),d(c)?(c.onload=m(a),c.onerror=n(a)):c.onreadystatechange=l(a),c.open(e,b,!0),v.cors.expected&&v.cors.sendCredentials&&!d(c)&&(c.withCredentials=!0),o(a),r("Sending "+e+" request for "+a),!s&&g?c.send(qq.obj2url(g,"")):c.send()}function k(a,b){var c=v.endpointStore.getEndpoint(a),d=u[a].addToPath;return void 0!=d&&(c+="/"+d),s&&b?qq.obj2url(b,c):c}function l(a){return function(){4===f(a).readyState&&h(a)}}function m(a){return function(){h(a)}}function n(a){return function(){h(a,!0)}}function o(a){var e=f(a),g=v.customHeaders;d(e)&&(v.cors.expected&&b()&&!c(g)||(e.setRequestHeader("X-Requested-With","XMLHttpRequest"),e.setRequestHeader("Cache-Control","no-cache"))),"POST"!==v.method&&"PUT"!==v.method||d(e)||e.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),d(e)||qq.each(g,function(a,b){e.setRequestHeader(a,b)})}function p(a){var b=f(a,!0),c=v.method;return b?(d(b)?(b.onerror=null,b.onload=null):b.onreadystatechange=null,b.abort(),g(a),r("Cancelled "+c+" for "+a),v.onCancel(a),!0):!1}function q(a){return qq.indexOf(v.successfulResponseCodes[v.method],a)>=0}var r,s,t=[],u=[],v={method:"POST",maxConnections:3,customHeaders:{},endpointStore:{},paramsStore:{},mandatedParams:{},successfulResponseCodes:{DELETE:[200,202,204],POST:[200,204]},cors:{expected:!1,sendCredentials:!1},log:function(){},onSend:function(){},onComplete:function(){},onCancel:function(){}};return qq.extend(v,a),r=v.log,s="GET"===v.method||"DELETE"===v.method,{send:function(a,b,c){u[a]={addToPath:b,additionalParams:c};var d=t.push(a);d<=v.maxConnections&&j(a)},cancel:function(a){return p(a)}}},qq.DeleteFileAjaxRequestor=function(a){"use strict";function b(){return f.method.toUpperCase()}function c(){return"POST"===b()?{_method:"DELETE"}:{}}var d,e=["POST","DELETE"],f={method:"DELETE",uuidParamName:"qquuid",endpointStore:{},maxConnections:3,customHeaders:{},paramsStore:{},demoMode:!1,cors:{expected:!1,sendCredentials:!1},log:function(){},onDelete:function(){},onDeleteComplete:function(){}};if(qq.extend(f,a),qq.indexOf(e,b())<0)throw new Error("'"+b()+"' is not a supported method for delete file requests!");return d=new qq.AjaxRequestor({method:b(),endpointStore:f.endpointStore,paramsStore:f.paramsStore,mandatedParams:c(),maxConnections:f.maxConnections,customHeaders:f.customHeaders,demoMode:f.demoMode,log:f.log,onSend:f.onDelete,onComplete:f.onDeleteComplete,cors:f.cors}),{sendDelete:function(a,c){var e={};f.log("Submitting delete file request for "+a),"DELETE"===b()?d.send(a,c):(e[f.uuidParamName]=c,d.send(a,null,e))}}},qq.WindowReceiveMessage=function(a){var b={log:function(){}},c={};return qq.extend(b,a),{receiveMessage:function(a,b){var d=function(a){b(a.data)};window.postMessage?c[a]=qq(window).attach("message",d):log("iframe message passing not supported in this browser!","error")},stopReceivingMessages:function(a){if(window.postMessage){var b=c[a];b&&b()}}}},qq.UploadHandler=function(a){"use strict";function b(a){var b,c=qq.indexOf(h,a),e=d.maxConnections;c>=0&&(h.splice(c,1),h.length>=e&&e>c&&(b=h[e-1],f.upload(b)))}function c(a){e("Cancelling "+a),d.paramsStore.remove(a),b(a)}var d,e,f,g,h=[];return d={debug:!1,forceMultipart:!0,paramsInBody:!1,paramsStore:{},endpointStore:{},filenameParam:"qqfilename",cors:{expected:!1,sendCredentials:!1},maxConnections:3,uuidParamName:"qquuid",totalFileSizeParamName:"qqtotalfilesize",chunking:{enabled:!1,partSize:2e6,paramNames:{partIndex:"qqpartindex",partByteOffset:"qqpartbyteoffset",chunkSize:"qqchunksize",totalParts:"qqtotalparts",filename:"qqfilename"}},resume:{enabled:!1,id:null,cookiesExpireIn:7,paramNames:{resuming:"qqresume"}},log:function(){},onProgress:function(){},onComplete:function(){},onCancel:function(){},onUpload:function(){},onUploadChunk:function(){},onAutoRetry:function(){},onResume:function(){},onUuidChanged:function(){}},qq.extend(d,a),e=d.log,f=qq.supportedFeatures.ajaxUploading?new qq.UploadHandlerXhr(d,b,d.onUuidChanged,e):new qq.UploadHandlerForm(d,b,d.onUuidChanged,e),g={add:function(a){return f.add(a)},upload:function(a){var b=h.push(a);return b<=d.maxConnections?(f.upload(a),!0):!1},retry:function(a){var b=qq.indexOf(h,a);return b>=0?f.upload(a,!0):this.upload(a)},cancel:function(a){var b=f.cancel(a);qq.isPromise(b)?b.then(function(){c(a)}):b!==!1&&c(a)},cancelAll:function(){var a=this,b=[];qq.extend(b,h),qq.each(b,function(b,c){a.cancel(c)}),h=[]},getName:function(a){return f.getName(a)},setName:function(a,b){f.setName(a,b)},getSize:function(a){return f.getSize?f.getSize(a):void 0},getFile:function(a){return f.getFile?f.getFile(a):void 0},reset:function(){e("Resetting upload handler"),g.cancelAll(),h=[],f.reset()},expunge:function(a){return f.expunge(a)},getUuid:function(a){return f.getUuid(a)},isValid:function(a){return f.isValid(a)},getResumableFilesData:function(){return f.getResumableFilesData?f.getResumableFilesData():[]}}},qq.UploadHandlerForm=function(a,b,c,d){"use strict";function e(a){void 0!==t[a]&&(t[a](),delete t[a])}function f(a,b){var c=a.id,d=m(c);y[r[d]]=b,t[d]=qq(a).attach("load",function(){q[d]&&(w("Received iframe load event for CORS upload request (iframe name "+c+")"),u[c]=setTimeout(function(){var a="No valid message received from loaded iframe for iframe name "+c;w(a,"error"),b({error:a})},1e3))}),x.receiveMessage(c,function(a){w("Received the following window message: '"+a+"'");var b,d=i(m(c),a),f=d.uuid;f&&y[f]?(w("Handling response for iframe name "+c),clearTimeout(u[c]),delete u[c],e(c),b=y[f],delete y[f],x.stopReceivingMessages(c),b(d)):f||w("'"+a+"' does not contain a UUID - ignoring.")})}function g(a,b){p.cors.expected?f(a,b):t[a.id]=qq(a).attach("load",function(){if(w("Received response for "+a.id),a.parentNode){try{if(a.contentDocument&&a.contentDocument.body&&"false"==a.contentDocument.body.innerHTML)return}catch(c){w("Error when attempting to access iframe during handling of upload response ("+c+")","error")}b()}})}function h(a,b){var c;try{var d=b.contentDocument||b.contentWindow.document,e=d.body.innerHTML;w("converting iframe's innerHTML to JSON"),w("innerHTML = "+e),e&&e.match(/^ ');return c.setAttribute("id",b),c.style.display="none",document.body.appendChild(c),c}function k(a,b){var c=p.paramsStore.getParams(a),d=p.demoMode?"GET":"POST",e=qq.toElement(''),f=p.endpointStore.getEndpoint(a),g=f;return c[p.uuidParamName]=r[a],void 0!==s[a]&&(c[p.filenameParam]=s[a]),p.paramsInBody?qq.obj2Inputs(c,e):g=qq.obj2url(c,f),e.setAttribute("action",g),e.setAttribute("target",b.name),e.style.display="none",document.body.appendChild(e),e}function l(a){delete q[a],delete r[a],delete t[a],p.cors.expected&&(clearTimeout(u[a]),delete u[a],x.stopReceivingMessages(a));var b=document.getElementById(n(a));b&&(b.setAttribute("src","java"+String.fromCharCode(115)+"cript:false;"),qq(b).remove())}function m(a){return a.split("_")[0]}function n(a){return a+"_"+z}var o,p=a,q=[],r=[],s=[],t={},u={},v=b,w=d,x=new qq.WindowReceiveMessage({log:w}),y={},z=qq.getUniqueId();return o={add:function(a){a.setAttribute("name",p.inputName);var b=q.push(a)-1;return r[b]=qq.getUniqueId(),a.parentNode&&qq(a).remove(),b},getName:function(a){return void 0!==s[a]?s[a]:o.isValid(a)?q[a].value.replace(/.*(\/|\\)/,""):(w(a+" is not a valid item ID.","error"),void 0)},setName:function(a,b){s[a]=b},isValid:function(a){return void 0!==q[a]},reset:function(){q=[],r=[],s=[],t={},z=qq.getUniqueId()},expunge:function(a){return l(a)},getUuid:function(a){return r[a]},cancel:function(a){var b=p.onCancel(a,o.getName(a));return qq.isPromise(b)?b.then(function(){l(a)}):b!==!1?(l(a),!0):!1},upload:function(a){var b,c=q[a],d=o.getName(a),f=j(a);if(!c)throw new Error("file with passed id was not added, or already uploaded or cancelled");p.onUpload(a,o.getName(a)),b=k(a,f),b.appendChild(c),g(f,function(b){w("iframe loaded");var c=b?b:h(a,f);e(a),p.cors.expected||qq(f).remove(),(c.success||!p.onAutoRetry(a,d,c))&&(p.onComplete(a,d,c),v(a))}),w("Sending upload request for "+a),b.submit(),qq(b).remove()}}},qq.UploadHandlerXhr=function(a,b,c,d){"use strict";function e(a,b,c){var d=K.getSize(a),e=K.getName(a);b[L.chunking.paramNames.partIndex]=c.part,b[L.chunking.paramNames.partByteOffset]=c.start,b[L.chunking.paramNames.chunkSize]=c.size,b[L.chunking.paramNames.totalParts]=c.count,b[L.totalFileSizeParamName]=d,T&&(b[L.filenameParam]=e)}function f(a){a[L.resume.paramNames.resuming]=!0}function g(a,b,c){return a.slice?a.slice(b,c):a.mozSlice?a.mozSlice(b,c):a.webkitSlice?a.webkitSlice(b,c):void 0}function h(a,b){var c=L.chunking.partSize,d=K.getSize(a),e=O[a].file||O[a].blobData.blob,f=c*b,h=f+c>=d?d:f+c,j=i(a);return{part:b,start:f,end:h,count:j,blob:g(e,f,h),size:h-f}}function i(a){var b=K.getSize(a),c=L.chunking.partSize;return Math.ceil(b/c)}function j(a){var b=new XMLHttpRequest;
+return O[a].xhr=b,b}function k(a,b,c,d){var e=new FormData,f=L.demoMode?"GET":"POST",g=L.endpointStore.getEndpoint(d),h=g,i=K.getName(d),j=K.getSize(d),k=O[d].blobData,l=O[d].newName;return a[L.uuidParamName]=O[d].uuid,T&&(a[L.totalFileSizeParamName]=j,k&&(a[L.filenameParam]=k.name)),void 0!==l&&(a[L.filenameParam]=l),L.paramsInBody||(T||(a[L.inputName]=l||i),h=qq.obj2url(a,g)),b.open(f,h,!0),L.cors.expected&&L.cors.sendCredentials&&(b.withCredentials=!0),T?(L.paramsInBody&&qq.obj2FormData(a,e),e.append(L.inputName,c),e):c}function l(a,b){var c=L.customHeaders,d=O[a].file||O[a].blobData.blob;b.setRequestHeader("X-Requested-With","XMLHttpRequest"),b.setRequestHeader("Cache-Control","no-cache"),T||(b.setRequestHeader("Content-Type","application/octet-stream"),b.setRequestHeader("X-Mime-Type",d.type)),qq.each(c,function(a,c){b.setRequestHeader(a,c)})}function m(a,b,c){var d=K.getName(a),e=K.getSize(a);O[a].attemptingResume=!1,L.onProgress(a,d,e,e),L.onComplete(a,d,b,c),O[a]&&delete O[a].xhr,M(a)}function n(a){var b,c,d=O[a].remainingChunkIdxs[0],g=h(a,d),i=j(a),m=K.getSize(a),n=K.getName(a);void 0===O[a].loaded&&(O[a].loaded=0),R&&O[a].file&&z(a,g),i.onreadystatechange=y(a,i),i.upload.onprogress=function(b){if(b.lengthComputable){var c=b.loaded+O[a].loaded,e=o(a,d,b.total);L.onProgress(a,n,c,e)}},L.onUploadChunk(a,n,x(g)),c=L.paramsStore.getParams(a),e(a,c,g),O[a].attemptingResume&&f(c),b=k(c,i,g.blob,a),l(a,i),N("Sending chunked upload request for item "+a+": bytes "+(g.start+1)+"-"+g.end+" of "+m),i.send(b)}function o(a,b,c){var d=h(a,b),e=d.size,f=c-e,g=K.getSize(a),i=d.count,j=O[a].initialRequestOverhead,k=f-j;return O[a].lastRequestOverhead=f,0===b?(O[a].lastChunkIdxProgress=0,O[a].initialRequestOverhead=f,O[a].estTotalRequestsSize=g+i*f):O[a].lastChunkIdxProgress!==b&&(O[a].lastChunkIdxProgress=b,O[a].estTotalRequestsSize+=k),O[a].estTotalRequestsSize}function p(a){return T?O[a].lastRequestOverhead:0}function q(a,b,c){var d=O[a].remainingChunkIdxs.shift(),e=h(a,d);O[a].attemptingResume=!1,O[a].loaded+=e.size+p(a),O[a].remainingChunkIdxs.length>0?n(a):(R&&A(a),m(a,b,c))}function r(a,b){return 200!==a.status||!b.success||b.reset}function s(a,b){var d;try{d=qq.parseJson(b.responseText),void 0!==d.newUuid&&(N("Server requested UUID change from '"+O[a].uuid+"' to '"+d.newUuid+"'"),O[a].uuid=d.newUuid,c(a,d.newUuid))}catch(e){N("Error when attempting to parse xhr response text ("+e+")","error"),d={}}return d}function t(a){N("Server has ordered chunking effort to be restarted on next attempt for item ID "+a,"error"),R&&(A(a),O[a].attemptingResume=!1),O[a].remainingChunkIdxs=[],delete O[a].loaded,delete O[a].estTotalRequestsSize,delete O[a].initialRequestOverhead}function u(a){O[a].attemptingResume=!1,N("Server has declared that it cannot handle resume for item ID "+a+" - starting from the first chunk","error"),t(a),K.upload(a,!0)}function v(a,b,c){var d=K.getName(a);L.onAutoRetry(a,d,b,c)||m(a,b,c)}function w(a,b){var c;O[a]&&(N("xhr - server response received for "+a),N("responseText = "+b.responseText),c=s(a,b),r(b,c)?(c.reset&&t(a),O[a].attemptingResume&&c.reset?u(a):v(a,c,b)):Q?q(a,c,b):m(a,c,b))}function x(a){return{partIndex:a.part,startByte:a.start+1,endByte:a.end,totalParts:a.count}}function y(a,b){return function(){4===b.readyState&&w(a,b)}}function z(a,b){var c=K.getUuid(a),d=O[a].loaded,e=O[a].initialRequestOverhead,f=O[a].estTotalRequestsSize,g=C(a),h=c+P+b.part+P+d+P+e+P+f,i=L.resume.cookiesExpireIn;qq.setCookie(g,h,i)}function A(a){if(O[a].file){var b=C(a);qq.deleteCookie(b)}}function B(a){var b,c,d,e,f,g,h=qq.getCookie(C(a)),i=K.getName(a);if(h){if(b=h.split(P),5===b.length)return c=b[0],d=parseInt(b[1],10),e=parseInt(b[2],10),f=parseInt(b[3],10),g=parseInt(b[4],10),{uuid:c,part:d,lastByteSent:e,initialRequestOverhead:f,estTotalRequestsSize:g};N("Ignoring previously stored resume/chunk cookie for "+i+" - old cookie format","warn")}}function C(a){var b,c=K.getName(a),d=K.getSize(a),e=L.chunking.partSize;return b="qqfilechunk"+P+encodeURIComponent(c)+P+d+P+e,void 0!==S&&(b+=P+S),b}function D(){return null===L.resume.id||void 0===L.resume.id||qq.isFunction(L.resume.id)||qq.isObject(L.resume.id)?void 0:L.resume.id}function E(a,b){var c;for(c=i(a)-1;c>=b;c-=1)O[a].remainingChunkIdxs.unshift(c);n(a)}function F(a,b,c,d){c=d.part,O[a].loaded=d.lastByteSent,O[a].estTotalRequestsSize=d.estTotalRequestsSize,O[a].initialRequestOverhead=d.initialRequestOverhead,O[a].attemptingResume=!0,N("Resuming "+b+" at partition index "+c),E(a,c)}function G(a,b,c){var d,e=K.getName(a),f=h(a,b.part);d=L.onResume(a,e,x(f)),qq.isPromise(d)?(N("Waiting for onResume promise to be fulfilled for "+a),d.then(function(){F(a,e,c,b)},function(){N("onResume promise fulfilled - failure indicated. Will not resume."),E(a,c)})):d!==!1?F(a,e,c,b):(N("onResume callback returned false. Will not resume."),E(a,c))}function H(a,b){var c,d=0;O[a].remainingChunkIdxs&&0!==O[a].remainingChunkIdxs.length?n(a):(O[a].remainingChunkIdxs=[],R&&!b&&O[a].file?(c=B(a),c?G(a,c,d):E(a,d)):E(a,d))}function I(a){var b,c,d,e=O[a].file||O[a].blobData.blob,f=K.getName(a);O[a].loaded=0,b=j(a),b.upload.onprogress=function(b){b.lengthComputable&&(O[a].loaded=b.loaded,L.onProgress(a,f,b.loaded,b.total))},b.onreadystatechange=y(a,b),c=L.paramsStore.getParams(a),d=k(c,b,e,a),l(a,b),N("Sending upload request for "+a),b.send(d)}function J(a){var b=O[a].xhr;b&&(b.onreadystatechange=null,b.abort()),R&&A(a),delete O[a]}var K,L=a,M=b,N=d,O=[],P="|",Q=L.chunking.enabled&&qq.supportedFeatures.chunking,R=L.resume.enabled&&Q&&qq.supportedFeatures.resume,S=D(),T=L.forceMultipart||L.paramsInBody;return K={add:function(a){var b,c,d=qq.getUniqueId();if(qq.isFile(a))b=O.push({file:a})-1;else{if(!qq.isBlob(a.blob))throw new Error("Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)");b=O.push({blobData:a})-1}return R&&(c=B(b),c&&(d=c.uuid)),O[b].uuid=d,b},getName:function(a){if(K.isValid(a)){var b=O[a].file,c=O[a].blobData,d=O[a].newName;return void 0!==d?d:b?null!==b.fileName&&void 0!==b.fileName?b.fileName:b.name:c.name}N(a+" is not a valid item ID.","error")},setName:function(a,b){O[a].newName=b},getSize:function(a){var b=O[a].file||O[a].blobData.blob;return qq.isFileOrInput(b)?null!=b.fileSize?b.fileSize:b.size:b.size},getFile:function(a){return O[a]?O[a].file||O[a].blobData.blob:void 0},isValid:function(a){return void 0!==O[a]},reset:function(){O=[]},expunge:function(a){return J(a)},getUuid:function(a){return O[a].uuid},upload:function(a,b){var c=this.getName(a);this.isValid(a)&&(L.onUpload(a,c),Q?H(a,b):I(a))},cancel:function(a){var b=L.onCancel(a,this.getName(a));return qq.isPromise(b)?b.then(function(){J(a)}):b!==!1?(J(a),!0):!1},getResumableFilesData:function(){var a=[],b=[];return Q&&R?(a=void 0===S?qq.getCookieNames(new RegExp("^qqfilechunk\\"+P+".+\\"+P+"\\d+\\"+P+L.chunking.partSize+"=")):qq.getCookieNames(new RegExp("^qqfilechunk\\"+P+".+\\"+P+"\\d+\\"+P+L.chunking.partSize+"\\"+P+S+"=")),qq.each(a,function(a,c){var d=c.split(P),e=qq.getCookie(c).split(P);b.push({name:decodeURIComponent(d[1]),size:d[2],uuid:e[0],partIdx:e[1]})}),b):[]}}},qq.UiEventHandler=function(a,b){"use strict";function c(a){d.attach(a,e.eventType,function(a){a=a||window.event;var b=a.target||a.srcElement;e.onHandled(b,a)})}var d=new qq.DisposeSupport,e={eventType:"click",attachTo:null,onHandled:function(){}},f={addHandler:function(a){c(a)},dispose:function(){d.dispose()}};return qq.extend(b,{getItemFromEventTarget:function(a){for(var b=a.parentNode;void 0===b.qqFileId;)b=b.parentNode;return b},getFileIdFromItem:function(a){return a.qqFileId},getDisposeSupport:function(){return d}}),qq.extend(e,a),e.attachTo&&c(e.attachTo),f},qq.DeleteRetryOrCancelClickHandler=function(a){"use strict";function b(a,b){if(qq(a).hasClass(e.classes.cancel)||qq(a).hasClass(e.classes.retry)||qq(a).hasClass(e.classes.deleteButton)){var f=d.getItemFromEventTarget(a),g=d.getFileIdFromItem(f);qq.preventDefault(b),e.log(qq.format("Detected valid cancel, retry, or delete click event on file '{}', ID: {}.",e.onGetName(g),g)),c(a,g)}}function c(a,b){qq(a).hasClass(e.classes.deleteButton)?e.onDeleteFile(b):qq(a).hasClass(e.classes.cancel)?e.onCancel(b):e.onRetry(b)}var d={},e={listElement:document,log:function(){},classes:{cancel:"qq-upload-cancel",deleteButton:"qq-upload-delete",retry:"qq-upload-retry"},onDeleteFile:function(){},onCancel:function(){},onRetry:function(){},onGetName:function(){}};qq.extend(e,a),e.eventType="click",e.onHandled=b,e.attachTo=e.listElement,qq.extend(this,new qq.UiEventHandler(e,d))},qq.FilenameEditHandler=function(a,b){"use strict";function c(a){var b=i.onGetName(a),c=b.lastIndexOf(".");return c>0&&(b=b.substr(0,c)),b}function d(a){var b=i.onGetName(a),c=b.lastIndexOf(".");return c>0?b.substr(c,b.length-c):void 0}function e(a,b){var c,e=a.value;void 0!==e&&qq.trimStr(e).length>0&&(c=d(b),void 0!==c&&(e+=d(b)),i.onSetName(b,e)),i.onEditingStatusChange(b,!1)}function f(a,c){b.getDisposeSupport().attach(a,"blur",function(){e(a,c)})}function g(a,c){b.getDisposeSupport().attach(a,"keyup",function(b){var d=b.keyCode||b.which;13===d&&e(a,c)})}var h,i={listElement:null,log:function(){},classes:{file:"qq-upload-file"},onGetUploadStatus:function(){},onGetName:function(){},onSetName:function(){},onGetInput:function(){},onEditingStatusChange:function(){}};return qq.extend(i,a),i.attachTo=i.listElement,h=qq.extend(this,new qq.UiEventHandler(i,b)),qq.extend(b,{handleFilenameEdit:function(a,b,d,e){var h=i.onGetInput(d);i.onEditingStatusChange(a,!0),h.value=c(a),e&&h.focus(),f(h,a),g(h,a)}}),h},qq.FilenameClickHandler=function(a){"use strict";function b(a,b){if(qq(a).hasClass(d.classes.file)||qq(a).hasClass(d.classes.editNameIcon)){var e=c.getItemFromEventTarget(a),f=c.getFileIdFromItem(e),g=d.onGetUploadStatus(f);g===qq.status.SUBMITTED&&(d.log(qq.format("Detected valid filename click event on file '{}', ID: {}.",d.onGetName(f),f)),qq.preventDefault(b),c.handleFilenameEdit(f,a,e,!0))}}var c={},d={log:function(){},classes:{file:"qq-upload-file",editNameIcon:"qq-edit-filename-icon"},onGetUploadStatus:function(){},onGetName:function(){}};return qq.extend(d,a),d.eventType="click",d.onHandled=b,qq.extend(this,new qq.FilenameEditHandler(d,c))},qq.FilenameInputFocusInHandler=function(a,b){"use strict";function c(a){if(qq(a).hasClass(d.classes.editFilenameInput)){var c=b.getItemFromEventTarget(a),e=b.getFileIdFromItem(c),f=d.onGetUploadStatus(e);f===qq.status.SUBMITTED&&(d.log(qq.format("Detected valid filename input focus event on file '{}', ID: {}.",d.onGetName(e),e)),b.handleFilenameEdit(e,a,c))}}var d={listElement:null,classes:{editFilenameInput:"qq-edit-filename"},onGetUploadStatus:function(){},log:function(){}};return b||(b={}),d.eventType="focusin",d.onHandled=c,qq.extend(d,a),qq.extend(this,new qq.FilenameEditHandler(d,b))},qq.FilenameInputFocusHandler=function(a){"use strict";return a.eventType="focus",a.attachTo=null,qq.extend(this,new qq.FilenameInputFocusInHandler(a,{}))};
+/*! 2013-07-16 */
diff --git a/ajax/libs/file-uploader/3.7.0/loading.gif b/ajax/libs/file-uploader/3.7.0/loading.gif
new file mode 100644
index 000000000..6fba77609
Binary files /dev/null and b/ajax/libs/file-uploader/3.7.0/loading.gif differ
diff --git a/ajax/libs/file-uploader/3.7.0/processing.gif b/ajax/libs/file-uploader/3.7.0/processing.gif
new file mode 100644
index 000000000..7c99504e1
Binary files /dev/null and b/ajax/libs/file-uploader/3.7.0/processing.gif differ
diff --git a/ajax/libs/file-uploader/package.json b/ajax/libs/file-uploader/package.json
index 535c87281..47d20538a 100755
--- a/ajax/libs/file-uploader/package.json
+++ b/ajax/libs/file-uploader/package.json
@@ -1,7 +1,7 @@
{
"name": "file-uploader",
"filename": "fineuploader.min.js",
- "version": "3.1.1",
+ "version": "3.7.0",
"description": "Multiple file upload plugin with progress-bar, drag-and-drop. ",
"homepage": "http://fineuploader.com",
"keywords": [