From 48705ceac4823deec625f80b57739bdc632967af Mon Sep 17 00:00:00 2001 From: jesfrk7 Date: Tue, 16 Jul 2013 13:44:27 -0500 Subject: [PATCH] Adding newer versions of fine-uploader, including 3.2.0, 3.3.0, 3.3.1, 3.4.0, 3.4.1, 3.5.0, 3.6.0, 3.6.1, 3.6.2, 3.6.3, 3.6.4, 3.7.0 Build locally using tagged sources from https://github.com/Widen/fine-uploader.git --- .../3.2.0/fineuploader-jquery.js | 3209 +++++++++ .../3.2.0/fineuploader-jquery.min.js | 13 + .../libs/file-uploader/3.2.0/fineuploader.css | 148 + ajax/libs/file-uploader/3.2.0/fineuploader.js | 3041 +++++++++ .../file-uploader/3.2.0/fineuploader.min.css | 1 + .../file-uploader/3.2.0/fineuploader.min.js | 13 + ajax/libs/file-uploader/3.2.0/loading.gif | Bin 0 -> 1688 bytes ajax/libs/file-uploader/3.2.0/processing.gif | Bin 0 -> 3209 bytes .../3.3.0/fineuploader-jquery.js | 3929 +++++++++++ .../3.3.0/fineuploader-jquery.min.js | 14 + .../libs/file-uploader/3.3.0/fineuploader.css | 148 + ajax/libs/file-uploader/3.3.0/fineuploader.js | 3761 +++++++++++ .../file-uploader/3.3.0/fineuploader.min.css | 1 + .../file-uploader/3.3.0/fineuploader.min.js | 14 + .../3.3.0/iframe.xss.response.js | 6 + .../3.3.0/iframe.xss.response.min.js | 1 + ajax/libs/file-uploader/3.3.0/loading.gif | Bin 0 -> 1688 bytes ajax/libs/file-uploader/3.3.0/processing.gif | Bin 0 -> 3209 bytes .../3.3.1/fineuploader-jquery.js | 4214 ++++++++++++ .../3.3.1/fineuploader-jquery.min.js | 13 + .../libs/file-uploader/3.3.1/fineuploader.css | 147 + ajax/libs/file-uploader/3.3.1/fineuploader.js | 4037 ++++++++++++ .../file-uploader/3.3.1/fineuploader.min.css | 1 + .../file-uploader/3.3.1/fineuploader.min.js | 13 + .../3.3.1/iframe.xss.response.js | 6 + .../3.3.1/iframe.xss.response.min.js | 1 + ajax/libs/file-uploader/3.3.1/loading.gif | Bin 0 -> 1688 bytes ajax/libs/file-uploader/3.3.1/processing.gif | Bin 0 -> 3209 bytes .../3.4.0/fineuploader-jquery.js | 4214 ++++++++++++ .../3.4.0/fineuploader-jquery.min.js | 13 + .../libs/file-uploader/3.4.0/fineuploader.css | 147 + ajax/libs/file-uploader/3.4.0/fineuploader.js | 4037 ++++++++++++ .../file-uploader/3.4.0/fineuploader.min.css | 1 + .../file-uploader/3.4.0/fineuploader.min.js | 13 + .../3.4.0/iframe.xss.response.js | 6 + .../3.4.0/iframe.xss.response.min.js | 1 + ajax/libs/file-uploader/3.4.0/loading.gif | Bin 0 -> 1688 bytes ajax/libs/file-uploader/3.4.0/processing.gif | Bin 0 -> 3209 bytes .../3.4.1/fineuploader-jquery.js | 4217 ++++++++++++ .../3.4.1/fineuploader-jquery.min.js | 13 + .../libs/file-uploader/3.4.1/fineuploader.css | 147 + ajax/libs/file-uploader/3.4.1/fineuploader.js | 4040 ++++++++++++ .../file-uploader/3.4.1/fineuploader.min.css | 1 + .../file-uploader/3.4.1/fineuploader.min.js | 13 + .../3.4.1/iframe.xss.response.js | 6 + .../3.4.1/iframe.xss.response.min.js | 1 + ajax/libs/file-uploader/3.4.1/loading.gif | Bin 0 -> 1688 bytes ajax/libs/file-uploader/3.4.1/processing.gif | Bin 0 -> 3209 bytes .../3.5.0/fineuploader-jquery.js | 4488 +++++++++++++ .../3.5.0/fineuploader-jquery.min.js | 13 + .../libs/file-uploader/3.5.0/fineuploader.css | 147 + ajax/libs/file-uploader/3.5.0/fineuploader.js | 4173 ++++++++++++ .../file-uploader/3.5.0/fineuploader.min.css | 1 + .../file-uploader/3.5.0/fineuploader.min.js | 13 + .../3.5.0/iframe.xss.response.js | 6 + .../3.5.0/iframe.xss.response.min.js | 1 + ajax/libs/file-uploader/3.5.0/loading.gif | Bin 0 -> 1688 bytes ajax/libs/file-uploader/3.5.0/processing.gif | Bin 0 -> 3209 bytes .../3.6.0/fineuploader-jquery.js | 5032 ++++++++++++++ .../3.6.0/fineuploader-jquery.min.js | 16 + .../libs/file-uploader/3.6.0/fineuploader.css | 147 + ajax/libs/file-uploader/3.6.0/fineuploader.js | 4717 +++++++++++++ .../file-uploader/3.6.0/fineuploader.min.css | 1 + .../file-uploader/3.6.0/fineuploader.min.js | 15 + .../3.6.0/iframe.xss.response.js | 6 + .../3.6.0/iframe.xss.response.min.js | 1 + ajax/libs/file-uploader/3.6.0/loading.gif | Bin 0 -> 1688 bytes ajax/libs/file-uploader/3.6.0/processing.gif | Bin 0 -> 3209 bytes .../3.6.1/fineuploader-jquery.js | 5039 ++++++++++++++ .../3.6.1/fineuploader-jquery.min.js | 16 + .../libs/file-uploader/3.6.1/fineuploader.css | 147 + ajax/libs/file-uploader/3.6.1/fineuploader.js | 4724 ++++++++++++++ .../file-uploader/3.6.1/fineuploader.min.css | 1 + .../file-uploader/3.6.1/fineuploader.min.js | 15 + .../3.6.1/iframe.xss.response.js | 6 + .../3.6.1/iframe.xss.response.min.js | 1 + ajax/libs/file-uploader/3.6.1/loading.gif | Bin 0 -> 1688 bytes ajax/libs/file-uploader/3.6.1/processing.gif | Bin 0 -> 3209 bytes .../3.6.2/fineuploader-jquery.js | 5039 ++++++++++++++ .../3.6.2/fineuploader-jquery.min.js | 16 + .../libs/file-uploader/3.6.2/fineuploader.css | 147 + ajax/libs/file-uploader/3.6.2/fineuploader.js | 4724 ++++++++++++++ .../file-uploader/3.6.2/fineuploader.min.css | 1 + .../file-uploader/3.6.2/fineuploader.min.js | 15 + .../3.6.2/iframe.xss.response.js | 6 + .../3.6.2/iframe.xss.response.min.js | 1 + ajax/libs/file-uploader/3.6.2/loading.gif | Bin 0 -> 1688 bytes ajax/libs/file-uploader/3.6.2/processing.gif | Bin 0 -> 3209 bytes .../3.6.3/fineuploader-jquery.js | 5039 ++++++++++++++ .../3.6.3/fineuploader-jquery.min.js | 16 + .../libs/file-uploader/3.6.3/fineuploader.css | 147 + ajax/libs/file-uploader/3.6.3/fineuploader.js | 4724 ++++++++++++++ .../file-uploader/3.6.3/fineuploader.min.css | 1 + .../file-uploader/3.6.3/fineuploader.min.js | 15 + .../3.6.3/iframe.xss.response.js | 6 + .../3.6.3/iframe.xss.response.min.js | 1 + ajax/libs/file-uploader/3.6.3/loading.gif | Bin 0 -> 1688 bytes ajax/libs/file-uploader/3.6.3/processing.gif | Bin 0 -> 3209 bytes .../3.6.4/fineuploader-jquery.js | 5039 ++++++++++++++ .../3.6.4/fineuploader-jquery.min.js | 16 + .../libs/file-uploader/3.6.4/fineuploader.css | 147 + ajax/libs/file-uploader/3.6.4/fineuploader.js | 4724 ++++++++++++++ .../file-uploader/3.6.4/fineuploader.min.css | 1 + .../file-uploader/3.6.4/fineuploader.min.js | 15 + .../3.6.4/iframe.xss.response.js | 6 + .../3.6.4/iframe.xss.response.min.js | 1 + ajax/libs/file-uploader/3.6.4/loading.gif | Bin 0 -> 1688 bytes ajax/libs/file-uploader/3.6.4/processing.gif | Bin 0 -> 3209 bytes ajax/libs/file-uploader/3.7.0/edit.gif | Bin 0 -> 145 bytes .../3.7.0/fineuploader-jquery.js | 5807 +++++++++++++++++ .../3.7.0/fineuploader-jquery.min.js | 19 + .../libs/file-uploader/3.7.0/fineuploader.css | 199 + ajax/libs/file-uploader/3.7.0/fineuploader.js | 5480 ++++++++++++++++ .../file-uploader/3.7.0/fineuploader.min.css | 19 + .../file-uploader/3.7.0/fineuploader.min.js | 19 + ajax/libs/file-uploader/3.7.0/loading.gif | Bin 0 -> 1688 bytes ajax/libs/file-uploader/3.7.0/processing.gif | Bin 0 -> 3209 bytes ajax/libs/file-uploader/package.json | 2 +- 118 files changed, 109718 insertions(+), 1 deletion(-) create mode 100644 ajax/libs/file-uploader/3.2.0/fineuploader-jquery.js create mode 100644 ajax/libs/file-uploader/3.2.0/fineuploader-jquery.min.js create mode 100644 ajax/libs/file-uploader/3.2.0/fineuploader.css create mode 100644 ajax/libs/file-uploader/3.2.0/fineuploader.js create mode 100644 ajax/libs/file-uploader/3.2.0/fineuploader.min.css create mode 100644 ajax/libs/file-uploader/3.2.0/fineuploader.min.js create mode 100644 ajax/libs/file-uploader/3.2.0/loading.gif create mode 100644 ajax/libs/file-uploader/3.2.0/processing.gif create mode 100644 ajax/libs/file-uploader/3.3.0/fineuploader-jquery.js create mode 100644 ajax/libs/file-uploader/3.3.0/fineuploader-jquery.min.js create mode 100644 ajax/libs/file-uploader/3.3.0/fineuploader.css create mode 100644 ajax/libs/file-uploader/3.3.0/fineuploader.js create mode 100644 ajax/libs/file-uploader/3.3.0/fineuploader.min.css create mode 100644 ajax/libs/file-uploader/3.3.0/fineuploader.min.js create mode 100644 ajax/libs/file-uploader/3.3.0/iframe.xss.response.js create mode 100644 ajax/libs/file-uploader/3.3.0/iframe.xss.response.min.js create mode 100644 ajax/libs/file-uploader/3.3.0/loading.gif create mode 100644 ajax/libs/file-uploader/3.3.0/processing.gif create mode 100644 ajax/libs/file-uploader/3.3.1/fineuploader-jquery.js create mode 100644 ajax/libs/file-uploader/3.3.1/fineuploader-jquery.min.js create mode 100644 ajax/libs/file-uploader/3.3.1/fineuploader.css create mode 100644 ajax/libs/file-uploader/3.3.1/fineuploader.js create mode 100644 ajax/libs/file-uploader/3.3.1/fineuploader.min.css create mode 100644 ajax/libs/file-uploader/3.3.1/fineuploader.min.js create mode 100644 ajax/libs/file-uploader/3.3.1/iframe.xss.response.js create mode 100644 ajax/libs/file-uploader/3.3.1/iframe.xss.response.min.js create mode 100644 ajax/libs/file-uploader/3.3.1/loading.gif create mode 100644 ajax/libs/file-uploader/3.3.1/processing.gif create mode 100644 ajax/libs/file-uploader/3.4.0/fineuploader-jquery.js create mode 100644 ajax/libs/file-uploader/3.4.0/fineuploader-jquery.min.js create mode 100644 ajax/libs/file-uploader/3.4.0/fineuploader.css create mode 100644 ajax/libs/file-uploader/3.4.0/fineuploader.js create mode 100644 ajax/libs/file-uploader/3.4.0/fineuploader.min.css create mode 100644 ajax/libs/file-uploader/3.4.0/fineuploader.min.js create mode 100644 ajax/libs/file-uploader/3.4.0/iframe.xss.response.js create mode 100644 ajax/libs/file-uploader/3.4.0/iframe.xss.response.min.js create mode 100644 ajax/libs/file-uploader/3.4.0/loading.gif create mode 100644 ajax/libs/file-uploader/3.4.0/processing.gif create mode 100644 ajax/libs/file-uploader/3.4.1/fineuploader-jquery.js create mode 100644 ajax/libs/file-uploader/3.4.1/fineuploader-jquery.min.js create mode 100644 ajax/libs/file-uploader/3.4.1/fineuploader.css create mode 100644 ajax/libs/file-uploader/3.4.1/fineuploader.js create mode 100644 ajax/libs/file-uploader/3.4.1/fineuploader.min.css create mode 100644 ajax/libs/file-uploader/3.4.1/fineuploader.min.js create mode 100644 ajax/libs/file-uploader/3.4.1/iframe.xss.response.js create mode 100644 ajax/libs/file-uploader/3.4.1/iframe.xss.response.min.js create mode 100644 ajax/libs/file-uploader/3.4.1/loading.gif create mode 100644 ajax/libs/file-uploader/3.4.1/processing.gif create mode 100644 ajax/libs/file-uploader/3.5.0/fineuploader-jquery.js create mode 100644 ajax/libs/file-uploader/3.5.0/fineuploader-jquery.min.js create mode 100644 ajax/libs/file-uploader/3.5.0/fineuploader.css create mode 100644 ajax/libs/file-uploader/3.5.0/fineuploader.js create mode 100644 ajax/libs/file-uploader/3.5.0/fineuploader.min.css create mode 100644 ajax/libs/file-uploader/3.5.0/fineuploader.min.js create mode 100644 ajax/libs/file-uploader/3.5.0/iframe.xss.response.js create mode 100644 ajax/libs/file-uploader/3.5.0/iframe.xss.response.min.js create mode 100644 ajax/libs/file-uploader/3.5.0/loading.gif create mode 100644 ajax/libs/file-uploader/3.5.0/processing.gif create mode 100644 ajax/libs/file-uploader/3.6.0/fineuploader-jquery.js create mode 100644 ajax/libs/file-uploader/3.6.0/fineuploader-jquery.min.js create mode 100644 ajax/libs/file-uploader/3.6.0/fineuploader.css create mode 100644 ajax/libs/file-uploader/3.6.0/fineuploader.js create mode 100644 ajax/libs/file-uploader/3.6.0/fineuploader.min.css create mode 100644 ajax/libs/file-uploader/3.6.0/fineuploader.min.js create mode 100644 ajax/libs/file-uploader/3.6.0/iframe.xss.response.js create mode 100644 ajax/libs/file-uploader/3.6.0/iframe.xss.response.min.js create mode 100644 ajax/libs/file-uploader/3.6.0/loading.gif create mode 100644 ajax/libs/file-uploader/3.6.0/processing.gif create mode 100644 ajax/libs/file-uploader/3.6.1/fineuploader-jquery.js create mode 100644 ajax/libs/file-uploader/3.6.1/fineuploader-jquery.min.js create mode 100644 ajax/libs/file-uploader/3.6.1/fineuploader.css create mode 100644 ajax/libs/file-uploader/3.6.1/fineuploader.js create mode 100644 ajax/libs/file-uploader/3.6.1/fineuploader.min.css create mode 100644 ajax/libs/file-uploader/3.6.1/fineuploader.min.js create mode 100644 ajax/libs/file-uploader/3.6.1/iframe.xss.response.js create mode 100644 ajax/libs/file-uploader/3.6.1/iframe.xss.response.min.js create mode 100644 ajax/libs/file-uploader/3.6.1/loading.gif create mode 100644 ajax/libs/file-uploader/3.6.1/processing.gif create mode 100644 ajax/libs/file-uploader/3.6.2/fineuploader-jquery.js create mode 100644 ajax/libs/file-uploader/3.6.2/fineuploader-jquery.min.js create mode 100644 ajax/libs/file-uploader/3.6.2/fineuploader.css create mode 100644 ajax/libs/file-uploader/3.6.2/fineuploader.js create mode 100644 ajax/libs/file-uploader/3.6.2/fineuploader.min.css create mode 100644 ajax/libs/file-uploader/3.6.2/fineuploader.min.js create mode 100644 ajax/libs/file-uploader/3.6.2/iframe.xss.response.js create mode 100644 ajax/libs/file-uploader/3.6.2/iframe.xss.response.min.js create mode 100644 ajax/libs/file-uploader/3.6.2/loading.gif create mode 100644 ajax/libs/file-uploader/3.6.2/processing.gif create mode 100644 ajax/libs/file-uploader/3.6.3/fineuploader-jquery.js create mode 100644 ajax/libs/file-uploader/3.6.3/fineuploader-jquery.min.js create mode 100644 ajax/libs/file-uploader/3.6.3/fineuploader.css create mode 100644 ajax/libs/file-uploader/3.6.3/fineuploader.js create mode 100644 ajax/libs/file-uploader/3.6.3/fineuploader.min.css create mode 100644 ajax/libs/file-uploader/3.6.3/fineuploader.min.js create mode 100644 ajax/libs/file-uploader/3.6.3/iframe.xss.response.js create mode 100644 ajax/libs/file-uploader/3.6.3/iframe.xss.response.min.js create mode 100644 ajax/libs/file-uploader/3.6.3/loading.gif create mode 100644 ajax/libs/file-uploader/3.6.3/processing.gif create mode 100644 ajax/libs/file-uploader/3.6.4/fineuploader-jquery.js create mode 100644 ajax/libs/file-uploader/3.6.4/fineuploader-jquery.min.js create mode 100644 ajax/libs/file-uploader/3.6.4/fineuploader.css create mode 100644 ajax/libs/file-uploader/3.6.4/fineuploader.js create mode 100644 ajax/libs/file-uploader/3.6.4/fineuploader.min.css create mode 100644 ajax/libs/file-uploader/3.6.4/fineuploader.min.js create mode 100644 ajax/libs/file-uploader/3.6.4/iframe.xss.response.js create mode 100644 ajax/libs/file-uploader/3.6.4/iframe.xss.response.min.js create mode 100644 ajax/libs/file-uploader/3.6.4/loading.gif create mode 100644 ajax/libs/file-uploader/3.6.4/processing.gif create mode 100644 ajax/libs/file-uploader/3.7.0/edit.gif create mode 100644 ajax/libs/file-uploader/3.7.0/fineuploader-jquery.js create mode 100644 ajax/libs/file-uploader/3.7.0/fineuploader-jquery.min.js create mode 100644 ajax/libs/file-uploader/3.7.0/fineuploader.css create mode 100644 ajax/libs/file-uploader/3.7.0/fineuploader.js create mode 100644 ajax/libs/file-uploader/3.7.0/fineuploader.min.css create mode 100644 ajax/libs/file-uploader/3.7.0/fineuploader.min.js create mode 100644 ajax/libs/file-uploader/3.7.0/loading.gif create mode 100644 ajax/libs/file-uploader/3.7.0/processing.gif 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 ? '
{uploadButtonText}
' : '') + + '{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?'
    {uploadButtonText}
    ':"")+'{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 ? '
      {uploadButtonText}
      ' : '') + + '{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?'
        {uploadButtonText}
        ':"")+'{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 0000000000000000000000000000000000000000..6fba77609ec50f9ab338a90d658e16bb05b8d206 GIT binary patch literal 1688 zcmb`{Z%i9?90u@T|8OmBp{1p?1LC6bjoZDt zkXep>N0_=eva4($!i&&_A= zW;rjn{OxjfHIq1=2=5CoTwNF*91gVyGpm_kvp>8yy0hlS_a|Ji)@kL>dI%ZU?-^tFX>XV|yhM;_kA{Hbj4_?z5yjh^aFnC>zj^ z_o#~Hj9!_H_*k7~=KW86b?g7){QF<}*1#h#Mg;I?KfvJ(Qn0`|`BAdFr+c0K))KhV z*VBp7xc^E2^r^{p+tmzG058~M{V~WRzNN315Fgk94t9hH^W?`#Bm#eA1pn#xrXL)h zI^;Ui`0^Wt{g;NKDbM;@HlU>SeVzx$S zD(zKa4PR~x=t+@VO553Pr?otfml2s$r5>svWlh@%v+uePKj_d>&Sdse`V8u3A-efVY*!3yWCkn;jGN z4p{|BeYkjFBuDAmvz64yq>ELiQR#^<2l$(TJ_(cKNgIz_Nhh1{G-^uptX@&fDfK2t z(V)gap5e@`c^bWergglzjkZ2b7t|ZiD2PIur&VL64kt(#wk%s zfpfc0(7HF(kPybcEeIz;ajkaEet4sf;8~k<7z?FDCO6U`$2I{oVnGgoggOchLhOqN zujBo1p!U0lG3Lc@doPaP9*MBWqJ6csgq&1;(|E<#M=8iVshFbW=dNn!XfpbUcOt5= zE9QQ%mv7-6?w%-TGfT~7?vfl`ue3$U8Hk1(Ar{J5VlbKLUbFb5$<}64dvvPYQY$gZ z-{o1=x%2J~N7H{eLJ-9RB~A-UI6@GPsO9tr(~w0oVGU3e5=_{g(8NjrC^SKdatd>N zZ_ojXLLvZ|&VD*8OgVP*-7I$V!nFk~4-nrk^8fCs{i$`wIdNfSjQ?CDCdQYDd}#4E zzO35eJ}t3O)RgwDxNBFV=}K*%X_Sk;Fy$B%yR;^qxMio+)4z}Q2rbDSRQTqiegkVMdcOh*)yl$90>MUHO; z$`puTM1;T!MiGo+m3fqAYLQvxXtjzut!L|YXxFvO(^-ylmi4qdoVq8Ac4uw>ME>Hx zocsO!zR&Zxvt0|GTSFoe9Yl!B<~B4nTzL0_!{wN~HyJw=vs$d>FO`q~c|5u=8u&$E z`rp$d!y^Wr0TW~4*6`T%v8gYo7Cpac;**Ks+TexJ3tT?e=kZNFn2Ha@)1OMOsjC_P zXgt;*3#<-Ie=|LDb|lgsxqa*Q!-o&i6ZkVfl;k+`^X!>Ro%83;zjs-6o^)Wvce| zZqW)6eWXI%a||6<_iJLECXwi6I=XNFG6NgG=#Yq4NL#MZ@>*%3MR=0z)YW;K6q>plt6OP!bKnhyus2S=!ILt)->zn-N}q+ zGe8?u0Syen(?A(O@=4I~f4jK7GAmuT%f$-Oix-9rsQwGX^R^x=-(%crz4R;MJ}y>l zKeUvUQop1B#KpJUwuV`6zW6EkX(G#{saf9Gt9M%9;sk*8foPYtGYPQrszc3jFH3UMy z10i6WSr7in#T`kBoH?VJ3qz?W<$=Ls>$@?}akN)(zrWgu+foz7K zr7WgvqXtKoTvk)m*p#MLhg=lWtlPn;D^W`$o82DiDtf~%)*$h2M{{gIrKa7a*sRZy zDG)|DRo2SmF4bF=0*57HW@x?OXh($aAp86WNS>d^rEmD#3i}jf8Y{vot`u=ya!Z*m zqGR-Y;?8o;4Gm{NIg72#eN18d4#n06$She3h!$c74M;9o4H!i*z<>zC1{Me#Vopr8 zD`Nl%sRKoF96}=i6B<P!NEHc7Pi&&J+lLcyZ*v6z(Og_pC1| zn_pPJf?R%h>EGjRsB$~+=_8|?4Q^CF@aBn{ofE9myQg#h@zwPG;5)qMZmrK8MrO6PEVU!6Gi5$LgB2Rt3I@0tB_!$zU!vV!e75p*n_mf zA-hbeyynY%Ja&JW19r>Z$;6oIkHKyM3xI%rz*Y+|VeIen~i2W>60 z+|K$D;&i}^PqD0R9ekZ)o=aI23sNg7k%8u=)n$td8nex$#mtBmip^fLFd%GI1Qd$QM2i&~%O*oUNC zMoth~rXLBn+r^l2_O5lvR3v^)D6cbK3K;FGL60CHIORyetslF$KK4lcPCc3$AYypU zurMHH7)nqE2r)6EWEg~-NE~)RX%I#hJa$D+BOuwEbifLD8Nee#mi~TETclLyNabIlb4}{ZBp$rtyd_QztL)1 zX-XAp#QmX||IonEkbV!^+vaPJoH`TY;be}~${`AP#%0(N(K1ef&Tm}Oi>!8)Ojbqu z)j1SS+v8e0W>i;&))tO1IHH5PmRzT$; zN^!BzRE-24V+F$N@=Pq@O0S92Qq;TOOOg&{PoG()Qpp%4n?2A=u0^Vt@%T_tJogl1 zJkv`YB@X8uxOC+TFXmGa`|_lSzpgEZRm2NOy-Lc^$#DK?Ch^>Ny~bN+eXiclkrJc6 z5c>QoL#Cu$|7wne{r&SWaZAC$1gXN>fSC@XD!DcwcHl^?U^t;Z8i#nH3s4*&fbd~= z0}3F5AoK!?1Eol;7FZ9%BcK4RfC5(mutI}ich7huk}mvs;=G+Ie^%6AmF+7=^_lG} zGq~ialo;-l{WQ1!;L#5^xcQj-Kt?&Md$;N-P^o&s0cyFiqhWpNO-T z@72qM0(>=PT9NU@zwl49tL`UDj%F~VXY=uL%^JyFY2%dra0(CRokd8j$Y92u+< zNF;*1^H;~Njp6JVn^X?lrRe7BHNWx7rR7DGLy3gyt2(Xrue6Hz>?*dl`?_8H%P+5I HqM!T+FLlTe literal 0 HcmV?d00001 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 ? '
          {uploadButtonText}
          ' : '') + + '{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?'
            {uploadButtonText}
            ':"")+'{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 ? '
              {uploadButtonText}
              ' : '') + + '{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?'
                {uploadButtonText}
                ':"")+'{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 0000000000000000000000000000000000000000..6fba77609ec50f9ab338a90d658e16bb05b8d206 GIT binary patch literal 1688 zcmb`{Z%i9?90u@T|8OmBp{1p?1LC6bjoZDt zkXep>N0_=eva4($!i&&_A= zW;rjn{OxjfHIq1=2=5CoTwNF*91gVyGpm_kvp>8yy0hlS_a|Ji)@kL>dI%ZU?-^tFX>XV|yhM;_kA{Hbj4_?z5yjh^aFnC>zj^ z_o#~Hj9!_H_*k7~=KW86b?g7){QF<}*1#h#Mg;I?KfvJ(Qn0`|`BAdFr+c0K))KhV z*VBp7xc^E2^r^{p+tmzG058~M{V~WRzNN315Fgk94t9hH^W?`#Bm#eA1pn#xrXL)h zI^;Ui`0^Wt{g;NKDbM;@HlU>SeVzx$S zD(zKa4PR~x=t+@VO553Pr?otfml2s$r5>svWlh@%v+uePKj_d>&Sdse`V8u3A-efVY*!3yWCkn;jGN z4p{|BeYkjFBuDAmvz64yq>ELiQR#^<2l$(TJ_(cKNgIz_Nhh1{G-^uptX@&fDfK2t z(V)gap5e@`c^bWergglzjkZ2b7t|ZiD2PIur&VL64kt(#wk%s zfpfc0(7HF(kPybcEeIz;ajkaEet4sf;8~k<7z?FDCO6U`$2I{oVnGgoggOchLhOqN zujBo1p!U0lG3Lc@doPaP9*MBWqJ6csgq&1;(|E<#M=8iVshFbW=dNn!XfpbUcOt5= zE9QQ%mv7-6?w%-TGfT~7?vfl`ue3$U8Hk1(Ar{J5VlbKLUbFb5$<}64dvvPYQY$gZ z-{o1=x%2J~N7H{eLJ-9RB~A-UI6@GPsO9tr(~w0oVGU3e5=_{g(8NjrC^SKdatd>N zZ_ojXLLvZ|&VD*8OgVP*-7I$V!nFk~4-nrk^8fCs{i$`wIdNfSjQ?CDCdQYDd}#4E zzO35eJ}t3O)RgwDxNBFV=}K*%X_Sk;Fy$B%yR;^qxMio+)4z}Q2rbDSRQTqiegkVMdcOh*)yl$90>MUHO; z$`puTM1;T!MiGo+m3fqAYLQvxXtjzut!L|YXxFvO(^-ylmi4qdoVq8Ac4uw>ME>Hx zocsO!zR&Zxvt0|GTSFoe9Yl!B<~B4nTzL0_!{wN~HyJw=vs$d>FO`q~c|5u=8u&$E z`rp$d!y^Wr0TW~4*6`T%v8gYo7Cpac;**Ks+TexJ3tT?e=kZNFn2Ha@)1OMOsjC_P zXgt;*3#<-Ie=|LDb|lgsxqa*Q!-o&i6ZkVfl;k+`^X!>Ro%83;zjs-6o^)Wvce| zZqW)6eWXI%a||6<_iJLECXwi6I=XNFG6NgG=#Yq4NL#MZ@>*%3MR=0z)YW;K6q>plt6OP!bKnhyus2S=!ILt)->zn-N}q+ zGe8?u0Syen(?A(O@=4I~f4jK7GAmuT%f$-Oix-9rsQwGX^R^x=-(%crz4R;MJ}y>l zKeUvUQop1B#KpJUwuV`6zW6EkX(G#{saf9Gt9M%9;sk*8foPYtGYPQrszc3jFH3UMy z10i6WSr7in#T`kBoH?VJ3qz?W<$=Ls>$@?}akN)(zrWgu+foz7K zr7WgvqXtKoTvk)m*p#MLhg=lWtlPn;D^W`$o82DiDtf~%)*$h2M{{gIrKa7a*sRZy zDG)|DRo2SmF4bF=0*57HW@x?OXh($aAp86WNS>d^rEmD#3i}jf8Y{vot`u=ya!Z*m zqGR-Y;?8o;4Gm{NIg72#eN18d4#n06$She3h!$c74M;9o4H!i*z<>zC1{Me#Vopr8 zD`Nl%sRKoF96}=i6B<P!NEHc7Pi&&J+lLcyZ*v6z(Og_pC1| zn_pPJf?R%h>EGjRsB$~+=_8|?4Q^CF@aBn{ofE9myQg#h@zwPG;5)qMZmrK8MrO6PEVU!6Gi5$LgB2Rt3I@0tB_!$zU!vV!e75p*n_mf zA-hbeyynY%Ja&JW19r>Z$;6oIkHKyM3xI%rz*Y+|VeIen~i2W>60 z+|K$D;&i}^PqD0R9ekZ)o=aI23sNg7k%8u=)n$td8nex$#mtBmip^fLFd%GI1Qd$QM2i&~%O*oUNC zMoth~rXLBn+r^l2_O5lvR3v^)D6cbK3K;FGL60CHIORyetslF$KK4lcPCc3$AYypU zurMHH7)nqE2r)6EWEg~-NE~)RX%I#hJa$D+BOuwEbifLD8Nee#mi~TETclLyNabIlb4}{ZBp$rtyd_QztL)1 zX-XAp#QmX||IonEkbV!^+vaPJoH`TY;be}~${`AP#%0(N(K1ef&Tm}Oi>!8)Ojbqu z)j1SS+v8e0W>i;&))tO1IHH5PmRzT$; zN^!BzRE-24V+F$N@=Pq@O0S92Qq;TOOOg&{PoG()Qpp%4n?2A=u0^Vt@%T_tJogl1 zJkv`YB@X8uxOC+TFXmGa`|_lSzpgEZRm2NOy-Lc^$#DK?Ch^>Ny~bN+eXiclkrJc6 z5c>QoL#Cu$|7wne{r&SWaZAC$1gXN>fSC@XD!DcwcHl^?U^t;Z8i#nH3s4*&fbd~= z0}3F5AoK!?1Eol;7FZ9%BcK4RfC5(mutI}ich7huk}mvs;=G+Ie^%6AmF+7=^_lG} zGq~ialo;-l{WQ1!;L#5^xcQj-Kt?&Md$;N-P^o&s0cyFiqhWpNO-T z@72qM0(>=PT9NU@zwl49tL`UDj%F~VXY=uL%^JyFY2%dra0(CRokd8j$Y92u+< zNF;*1^H;~Njp6JVn^X?lrRe7BHNWx7rR7DGLy3gyt2(Xrue6Hz>?*dl`?_8H%P+5I HqM!T+FLlTe literal 0 HcmV?d00001 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 ? '
                  {uploadButtonText}
                  ' : '') + + '{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?'
                    {uploadButtonText}
                    ':"")+'{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 ? '
                      {uploadButtonText}
                      ' : '') + + '{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?'
                        {uploadButtonText}
                        ':"")+'{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 0000000000000000000000000000000000000000..6fba77609ec50f9ab338a90d658e16bb05b8d206 GIT binary patch literal 1688 zcmb`{Z%i9?90u@T|8OmBp{1p?1LC6bjoZDt zkXep>N0_=eva4($!i&&_A= zW;rjn{OxjfHIq1=2=5CoTwNF*91gVyGpm_kvp>8yy0hlS_a|Ji)@kL>dI%ZU?-^tFX>XV|yhM;_kA{Hbj4_?z5yjh^aFnC>zj^ z_o#~Hj9!_H_*k7~=KW86b?g7){QF<}*1#h#Mg;I?KfvJ(Qn0`|`BAdFr+c0K))KhV z*VBp7xc^E2^r^{p+tmzG058~M{V~WRzNN315Fgk94t9hH^W?`#Bm#eA1pn#xrXL)h zI^;Ui`0^Wt{g;NKDbM;@HlU>SeVzx$S zD(zKa4PR~x=t+@VO553Pr?otfml2s$r5>svWlh@%v+uePKj_d>&Sdse`V8u3A-efVY*!3yWCkn;jGN z4p{|BeYkjFBuDAmvz64yq>ELiQR#^<2l$(TJ_(cKNgIz_Nhh1{G-^uptX@&fDfK2t z(V)gap5e@`c^bWergglzjkZ2b7t|ZiD2PIur&VL64kt(#wk%s zfpfc0(7HF(kPybcEeIz;ajkaEet4sf;8~k<7z?FDCO6U`$2I{oVnGgoggOchLhOqN zujBo1p!U0lG3Lc@doPaP9*MBWqJ6csgq&1;(|E<#M=8iVshFbW=dNn!XfpbUcOt5= zE9QQ%mv7-6?w%-TGfT~7?vfl`ue3$U8Hk1(Ar{J5VlbKLUbFb5$<}64dvvPYQY$gZ z-{o1=x%2J~N7H{eLJ-9RB~A-UI6@GPsO9tr(~w0oVGU3e5=_{g(8NjrC^SKdatd>N zZ_ojXLLvZ|&VD*8OgVP*-7I$V!nFk~4-nrk^8fCs{i$`wIdNfSjQ?CDCdQYDd}#4E zzO35eJ}t3O)RgwDxNBFV=}K*%X_Sk;Fy$B%yR;^qxMio+)4z}Q2rbDSRQTqiegkVMdcOh*)yl$90>MUHO; z$`puTM1;T!MiGo+m3fqAYLQvxXtjzut!L|YXxFvO(^-ylmi4qdoVq8Ac4uw>ME>Hx zocsO!zR&Zxvt0|GTSFoe9Yl!B<~B4nTzL0_!{wN~HyJw=vs$d>FO`q~c|5u=8u&$E z`rp$d!y^Wr0TW~4*6`T%v8gYo7Cpac;**Ks+TexJ3tT?e=kZNFn2Ha@)1OMOsjC_P zXgt;*3#<-Ie=|LDb|lgsxqa*Q!-o&i6ZkVfl;k+`^X!>Ro%83;zjs-6o^)Wvce| zZqW)6eWXI%a||6<_iJLECXwi6I=XNFG6NgG=#Yq4NL#MZ@>*%3MR=0z)YW;K6q>plt6OP!bKnhyus2S=!ILt)->zn-N}q+ zGe8?u0Syen(?A(O@=4I~f4jK7GAmuT%f$-Oix-9rsQwGX^R^x=-(%crz4R;MJ}y>l zKeUvUQop1B#KpJUwuV`6zW6EkX(G#{saf9Gt9M%9;sk*8foPYtGYPQrszc3jFH3UMy z10i6WSr7in#T`kBoH?VJ3qz?W<$=Ls>$@?}akN)(zrWgu+foz7K zr7WgvqXtKoTvk)m*p#MLhg=lWtlPn;D^W`$o82DiDtf~%)*$h2M{{gIrKa7a*sRZy zDG)|DRo2SmF4bF=0*57HW@x?OXh($aAp86WNS>d^rEmD#3i}jf8Y{vot`u=ya!Z*m zqGR-Y;?8o;4Gm{NIg72#eN18d4#n06$She3h!$c74M;9o4H!i*z<>zC1{Me#Vopr8 zD`Nl%sRKoF96}=i6B<P!NEHc7Pi&&J+lLcyZ*v6z(Og_pC1| zn_pPJf?R%h>EGjRsB$~+=_8|?4Q^CF@aBn{ofE9myQg#h@zwPG;5)qMZmrK8MrO6PEVU!6Gi5$LgB2Rt3I@0tB_!$zU!vV!e75p*n_mf zA-hbeyynY%Ja&JW19r>Z$;6oIkHKyM3xI%rz*Y+|VeIen~i2W>60 z+|K$D;&i}^PqD0R9ekZ)o=aI23sNg7k%8u=)n$td8nex$#mtBmip^fLFd%GI1Qd$QM2i&~%O*oUNC zMoth~rXLBn+r^l2_O5lvR3v^)D6cbK3K;FGL60CHIORyetslF$KK4lcPCc3$AYypU zurMHH7)nqE2r)6EWEg~-NE~)RX%I#hJa$D+BOuwEbifLD8Nee#mi~TETclLyNabIlb4}{ZBp$rtyd_QztL)1 zX-XAp#QmX||IonEkbV!^+vaPJoH`TY;be}~${`AP#%0(N(K1ef&Tm}Oi>!8)Ojbqu z)j1SS+v8e0W>i;&))tO1IHH5PmRzT$; zN^!BzRE-24V+F$N@=Pq@O0S92Qq;TOOOg&{PoG()Qpp%4n?2A=u0^Vt@%T_tJogl1 zJkv`YB@X8uxOC+TFXmGa`|_lSzpgEZRm2NOy-Lc^$#DK?Ch^>Ny~bN+eXiclkrJc6 z5c>QoL#Cu$|7wne{r&SWaZAC$1gXN>fSC@XD!DcwcHl^?U^t;Z8i#nH3s4*&fbd~= z0}3F5AoK!?1Eol;7FZ9%BcK4RfC5(mutI}ich7huk}mvs;=G+Ie^%6AmF+7=^_lG} zGq~ialo;-l{WQ1!;L#5^xcQj-Kt?&Md$;N-P^o&s0cyFiqhWpNO-T z@72qM0(>=PT9NU@zwl49tL`UDj%F~VXY=uL%^JyFY2%dra0(CRokd8j$Y92u+< zNF;*1^H;~Njp6JVn^X?lrRe7BHNWx7rR7DGLy3gyt2(Xrue6Hz>?*dl`?_8H%P+5I HqM!T+FLlTe literal 0 HcmV?d00001 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 ? '
                          {uploadButtonText}
                          ' : '') + + '{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?'
                            {uploadButtonText}
                            ':"")+'{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 ? '
                              {uploadButtonText}
                              ' : '') + + '{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?'
                                {uploadButtonText}
                                ':"")+'{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 0000000000000000000000000000000000000000..6fba77609ec50f9ab338a90d658e16bb05b8d206 GIT binary patch literal 1688 zcmb`{Z%i9?90u@T|8OmBp{1p?1LC6bjoZDt zkXep>N0_=eva4($!i&&_A= zW;rjn{OxjfHIq1=2=5CoTwNF*91gVyGpm_kvp>8yy0hlS_a|Ji)@kL>dI%ZU?-^tFX>XV|yhM;_kA{Hbj4_?z5yjh^aFnC>zj^ z_o#~Hj9!_H_*k7~=KW86b?g7){QF<}*1#h#Mg;I?KfvJ(Qn0`|`BAdFr+c0K))KhV z*VBp7xc^E2^r^{p+tmzG058~M{V~WRzNN315Fgk94t9hH^W?`#Bm#eA1pn#xrXL)h zI^;Ui`0^Wt{g;NKDbM;@HlU>SeVzx$S zD(zKa4PR~x=t+@VO553Pr?otfml2s$r5>svWlh@%v+uePKj_d>&Sdse`V8u3A-efVY*!3yWCkn;jGN z4p{|BeYkjFBuDAmvz64yq>ELiQR#^<2l$(TJ_(cKNgIz_Nhh1{G-^uptX@&fDfK2t z(V)gap5e@`c^bWergglzjkZ2b7t|ZiD2PIur&VL64kt(#wk%s zfpfc0(7HF(kPybcEeIz;ajkaEet4sf;8~k<7z?FDCO6U`$2I{oVnGgoggOchLhOqN zujBo1p!U0lG3Lc@doPaP9*MBWqJ6csgq&1;(|E<#M=8iVshFbW=dNn!XfpbUcOt5= zE9QQ%mv7-6?w%-TGfT~7?vfl`ue3$U8Hk1(Ar{J5VlbKLUbFb5$<}64dvvPYQY$gZ z-{o1=x%2J~N7H{eLJ-9RB~A-UI6@GPsO9tr(~w0oVGU3e5=_{g(8NjrC^SKdatd>N zZ_ojXLLvZ|&VD*8OgVP*-7I$V!nFk~4-nrk^8fCs{i$`wIdNfSjQ?CDCdQYDd}#4E zzO35eJ}t3O)RgwDxNBFV=}K*%X_Sk;Fy$B%yR;^qxMio+)4z}Q2rbDSRQTqiegkVMdcOh*)yl$90>MUHO; z$`puTM1;T!MiGo+m3fqAYLQvxXtjzut!L|YXxFvO(^-ylmi4qdoVq8Ac4uw>ME>Hx zocsO!zR&Zxvt0|GTSFoe9Yl!B<~B4nTzL0_!{wN~HyJw=vs$d>FO`q~c|5u=8u&$E z`rp$d!y^Wr0TW~4*6`T%v8gYo7Cpac;**Ks+TexJ3tT?e=kZNFn2Ha@)1OMOsjC_P zXgt;*3#<-Ie=|LDb|lgsxqa*Q!-o&i6ZkVfl;k+`^X!>Ro%83;zjs-6o^)Wvce| zZqW)6eWXI%a||6<_iJLECXwi6I=XNFG6NgG=#Yq4NL#MZ@>*%3MR=0z)YW;K6q>plt6OP!bKnhyus2S=!ILt)->zn-N}q+ zGe8?u0Syen(?A(O@=4I~f4jK7GAmuT%f$-Oix-9rsQwGX^R^x=-(%crz4R;MJ}y>l zKeUvUQop1B#KpJUwuV`6zW6EkX(G#{saf9Gt9M%9;sk*8foPYtGYPQrszc3jFH3UMy z10i6WSr7in#T`kBoH?VJ3qz?W<$=Ls>$@?}akN)(zrWgu+foz7K zr7WgvqXtKoTvk)m*p#MLhg=lWtlPn;D^W`$o82DiDtf~%)*$h2M{{gIrKa7a*sRZy zDG)|DRo2SmF4bF=0*57HW@x?OXh($aAp86WNS>d^rEmD#3i}jf8Y{vot`u=ya!Z*m zqGR-Y;?8o;4Gm{NIg72#eN18d4#n06$She3h!$c74M;9o4H!i*z<>zC1{Me#Vopr8 zD`Nl%sRKoF96}=i6B<P!NEHc7Pi&&J+lLcyZ*v6z(Og_pC1| zn_pPJf?R%h>EGjRsB$~+=_8|?4Q^CF@aBn{ofE9myQg#h@zwPG;5)qMZmrK8MrO6PEVU!6Gi5$LgB2Rt3I@0tB_!$zU!vV!e75p*n_mf zA-hbeyynY%Ja&JW19r>Z$;6oIkHKyM3xI%rz*Y+|VeIen~i2W>60 z+|K$D;&i}^PqD0R9ekZ)o=aI23sNg7k%8u=)n$td8nex$#mtBmip^fLFd%GI1Qd$QM2i&~%O*oUNC zMoth~rXLBn+r^l2_O5lvR3v^)D6cbK3K;FGL60CHIORyetslF$KK4lcPCc3$AYypU zurMHH7)nqE2r)6EWEg~-NE~)RX%I#hJa$D+BOuwEbifLD8Nee#mi~TETclLyNabIlb4}{ZBp$rtyd_QztL)1 zX-XAp#QmX||IonEkbV!^+vaPJoH`TY;be}~${`AP#%0(N(K1ef&Tm}Oi>!8)Ojbqu z)j1SS+v8e0W>i;&))tO1IHH5PmRzT$; zN^!BzRE-24V+F$N@=Pq@O0S92Qq;TOOOg&{PoG()Qpp%4n?2A=u0^Vt@%T_tJogl1 zJkv`YB@X8uxOC+TFXmGa`|_lSzpgEZRm2NOy-Lc^$#DK?Ch^>Ny~bN+eXiclkrJc6 z5c>QoL#Cu$|7wne{r&SWaZAC$1gXN>fSC@XD!DcwcHl^?U^t;Z8i#nH3s4*&fbd~= z0}3F5AoK!?1Eol;7FZ9%BcK4RfC5(mutI}ich7huk}mvs;=G+Ie^%6AmF+7=^_lG} zGq~ialo;-l{WQ1!;L#5^xcQj-Kt?&Md$;N-P^o&s0cyFiqhWpNO-T z@72qM0(>=PT9NU@zwl49tL`UDj%F~VXY=uL%^JyFY2%dra0(CRokd8j$Y92u+< zNF;*1^H;~Njp6JVn^X?lrRe7BHNWx7rR7DGLy3gyt2(Xrue6Hz>?*dl`?_8H%P+5I HqM!T+FLlTe literal 0 HcmV?d00001 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 ? '
                                  {uploadButtonText}
                                  ' : '') + + '{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?'
                                    {uploadButtonText}
                                    ':"")+'{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 ? '
                                      {uploadButtonText}
                                      ' : '') + + '{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?'
                                        {uploadButtonText}
                                        ':"")+'{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 0000000000000000000000000000000000000000..6fba77609ec50f9ab338a90d658e16bb05b8d206 GIT binary patch literal 1688 zcmb`{Z%i9?90u@T|8OmBp{1p?1LC6bjoZDt zkXep>N0_=eva4($!i&&_A= zW;rjn{OxjfHIq1=2=5CoTwNF*91gVyGpm_kvp>8yy0hlS_a|Ji)@kL>dI%ZU?-^tFX>XV|yhM;_kA{Hbj4_?z5yjh^aFnC>zj^ z_o#~Hj9!_H_*k7~=KW86b?g7){QF<}*1#h#Mg;I?KfvJ(Qn0`|`BAdFr+c0K))KhV z*VBp7xc^E2^r^{p+tmzG058~M{V~WRzNN315Fgk94t9hH^W?`#Bm#eA1pn#xrXL)h zI^;Ui`0^Wt{g;NKDbM;@HlU>SeVzx$S zD(zKa4PR~x=t+@VO553Pr?otfml2s$r5>svWlh@%v+uePKj_d>&Sdse`V8u3A-efVY*!3yWCkn;jGN z4p{|BeYkjFBuDAmvz64yq>ELiQR#^<2l$(TJ_(cKNgIz_Nhh1{G-^uptX@&fDfK2t z(V)gap5e@`c^bWergglzjkZ2b7t|ZiD2PIur&VL64kt(#wk%s zfpfc0(7HF(kPybcEeIz;ajkaEet4sf;8~k<7z?FDCO6U`$2I{oVnGgoggOchLhOqN zujBo1p!U0lG3Lc@doPaP9*MBWqJ6csgq&1;(|E<#M=8iVshFbW=dNn!XfpbUcOt5= zE9QQ%mv7-6?w%-TGfT~7?vfl`ue3$U8Hk1(Ar{J5VlbKLUbFb5$<}64dvvPYQY$gZ z-{o1=x%2J~N7H{eLJ-9RB~A-UI6@GPsO9tr(~w0oVGU3e5=_{g(8NjrC^SKdatd>N zZ_ojXLLvZ|&VD*8OgVP*-7I$V!nFk~4-nrk^8fCs{i$`wIdNfSjQ?CDCdQYDd}#4E zzO35eJ}t3O)RgwDxNBFV=}K*%X_Sk;Fy$B%yR;^qxMio+)4z}Q2rbDSRQTqiegkVMdcOh*)yl$90>MUHO; z$`puTM1;T!MiGo+m3fqAYLQvxXtjzut!L|YXxFvO(^-ylmi4qdoVq8Ac4uw>ME>Hx zocsO!zR&Zxvt0|GTSFoe9Yl!B<~B4nTzL0_!{wN~HyJw=vs$d>FO`q~c|5u=8u&$E z`rp$d!y^Wr0TW~4*6`T%v8gYo7Cpac;**Ks+TexJ3tT?e=kZNFn2Ha@)1OMOsjC_P zXgt;*3#<-Ie=|LDb|lgsxqa*Q!-o&i6ZkVfl;k+`^X!>Ro%83;zjs-6o^)Wvce| zZqW)6eWXI%a||6<_iJLECXwi6I=XNFG6NgG=#Yq4NL#MZ@>*%3MR=0z)YW;K6q>plt6OP!bKnhyus2S=!ILt)->zn-N}q+ zGe8?u0Syen(?A(O@=4I~f4jK7GAmuT%f$-Oix-9rsQwGX^R^x=-(%crz4R;MJ}y>l zKeUvUQop1B#KpJUwuV`6zW6EkX(G#{saf9Gt9M%9;sk*8foPYtGYPQrszc3jFH3UMy z10i6WSr7in#T`kBoH?VJ3qz?W<$=Ls>$@?}akN)(zrWgu+foz7K zr7WgvqXtKoTvk)m*p#MLhg=lWtlPn;D^W`$o82DiDtf~%)*$h2M{{gIrKa7a*sRZy zDG)|DRo2SmF4bF=0*57HW@x?OXh($aAp86WNS>d^rEmD#3i}jf8Y{vot`u=ya!Z*m zqGR-Y;?8o;4Gm{NIg72#eN18d4#n06$She3h!$c74M;9o4H!i*z<>zC1{Me#Vopr8 zD`Nl%sRKoF96}=i6B<P!NEHc7Pi&&J+lLcyZ*v6z(Og_pC1| zn_pPJf?R%h>EGjRsB$~+=_8|?4Q^CF@aBn{ofE9myQg#h@zwPG;5)qMZmrK8MrO6PEVU!6Gi5$LgB2Rt3I@0tB_!$zU!vV!e75p*n_mf zA-hbeyynY%Ja&JW19r>Z$;6oIkHKyM3xI%rz*Y+|VeIen~i2W>60 z+|K$D;&i}^PqD0R9ekZ)o=aI23sNg7k%8u=)n$td8nex$#mtBmip^fLFd%GI1Qd$QM2i&~%O*oUNC zMoth~rXLBn+r^l2_O5lvR3v^)D6cbK3K;FGL60CHIORyetslF$KK4lcPCc3$AYypU zurMHH7)nqE2r)6EWEg~-NE~)RX%I#hJa$D+BOuwEbifLD8Nee#mi~TETclLyNabIlb4}{ZBp$rtyd_QztL)1 zX-XAp#QmX||IonEkbV!^+vaPJoH`TY;be}~${`AP#%0(N(K1ef&Tm}Oi>!8)Ojbqu z)j1SS+v8e0W>i;&))tO1IHH5PmRzT$; zN^!BzRE-24V+F$N@=Pq@O0S92Qq;TOOOg&{PoG()Qpp%4n?2A=u0^Vt@%T_tJogl1 zJkv`YB@X8uxOC+TFXmGa`|_lSzpgEZRm2NOy-Lc^$#DK?Ch^>Ny~bN+eXiclkrJc6 z5c>QoL#Cu$|7wne{r&SWaZAC$1gXN>fSC@XD!DcwcHl^?U^t;Z8i#nH3s4*&fbd~= z0}3F5AoK!?1Eol;7FZ9%BcK4RfC5(mutI}ich7huk}mvs;=G+Ie^%6AmF+7=^_lG} zGq~ialo;-l{WQ1!;L#5^xcQj-Kt?&Md$;N-P^o&s0cyFiqhWpNO-T z@72qM0(>=PT9NU@zwl49tL`UDj%F~VXY=uL%^JyFY2%dra0(CRokd8j$Y92u+< zNF;*1^H;~Njp6JVn^X?lrRe7BHNWx7rR7DGLy3gyt2(Xrue6Hz>?*dl`?_8H%P+5I HqM!T+FLlTe literal 0 HcmV?d00001 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 ? '
                                          {uploadButtonText}
                                          ' : '') + + '{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?'
                                            {uploadButtonText}
                                            ':"")+'{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 ? '
                                              {uploadButtonText}
                                              ' : '') + + '{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?'
                                                {uploadButtonText}
                                                ':"")+'{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 0000000000000000000000000000000000000000..6fba77609ec50f9ab338a90d658e16bb05b8d206 GIT binary patch literal 1688 zcmb`{Z%i9?90u@T|8OmBp{1p?1LC6bjoZDt zkXep>N0_=eva4($!i&&_A= zW;rjn{OxjfHIq1=2=5CoTwNF*91gVyGpm_kvp>8yy0hlS_a|Ji)@kL>dI%ZU?-^tFX>XV|yhM;_kA{Hbj4_?z5yjh^aFnC>zj^ z_o#~Hj9!_H_*k7~=KW86b?g7){QF<}*1#h#Mg;I?KfvJ(Qn0`|`BAdFr+c0K))KhV z*VBp7xc^E2^r^{p+tmzG058~M{V~WRzNN315Fgk94t9hH^W?`#Bm#eA1pn#xrXL)h zI^;Ui`0^Wt{g;NKDbM;@HlU>SeVzx$S zD(zKa4PR~x=t+@VO553Pr?otfml2s$r5>svWlh@%v+uePKj_d>&Sdse`V8u3A-efVY*!3yWCkn;jGN z4p{|BeYkjFBuDAmvz64yq>ELiQR#^<2l$(TJ_(cKNgIz_Nhh1{G-^uptX@&fDfK2t z(V)gap5e@`c^bWergglzjkZ2b7t|ZiD2PIur&VL64kt(#wk%s zfpfc0(7HF(kPybcEeIz;ajkaEet4sf;8~k<7z?FDCO6U`$2I{oVnGgoggOchLhOqN zujBo1p!U0lG3Lc@doPaP9*MBWqJ6csgq&1;(|E<#M=8iVshFbW=dNn!XfpbUcOt5= zE9QQ%mv7-6?w%-TGfT~7?vfl`ue3$U8Hk1(Ar{J5VlbKLUbFb5$<}64dvvPYQY$gZ z-{o1=x%2J~N7H{eLJ-9RB~A-UI6@GPsO9tr(~w0oVGU3e5=_{g(8NjrC^SKdatd>N zZ_ojXLLvZ|&VD*8OgVP*-7I$V!nFk~4-nrk^8fCs{i$`wIdNfSjQ?CDCdQYDd}#4E zzO35eJ}t3O)RgwDxNBFV=}K*%X_Sk;Fy$B%yR;^qxMio+)4z}Q2rbDSRQTqiegkVMdcOh*)yl$90>MUHO; z$`puTM1;T!MiGo+m3fqAYLQvxXtjzut!L|YXxFvO(^-ylmi4qdoVq8Ac4uw>ME>Hx zocsO!zR&Zxvt0|GTSFoe9Yl!B<~B4nTzL0_!{wN~HyJw=vs$d>FO`q~c|5u=8u&$E z`rp$d!y^Wr0TW~4*6`T%v8gYo7Cpac;**Ks+TexJ3tT?e=kZNFn2Ha@)1OMOsjC_P zXgt;*3#<-Ie=|LDb|lgsxqa*Q!-o&i6ZkVfl;k+`^X!>Ro%83;zjs-6o^)Wvce| zZqW)6eWXI%a||6<_iJLECXwi6I=XNFG6NgG=#Yq4NL#MZ@>*%3MR=0z)YW;K6q>plt6OP!bKnhyus2S=!ILt)->zn-N}q+ zGe8?u0Syen(?A(O@=4I~f4jK7GAmuT%f$-Oix-9rsQwGX^R^x=-(%crz4R;MJ}y>l zKeUvUQop1B#KpJUwuV`6zW6EkX(G#{saf9Gt9M%9;sk*8foPYtGYPQrszc3jFH3UMy z10i6WSr7in#T`kBoH?VJ3qz?W<$=Ls>$@?}akN)(zrWgu+foz7K zr7WgvqXtKoTvk)m*p#MLhg=lWtlPn;D^W`$o82DiDtf~%)*$h2M{{gIrKa7a*sRZy zDG)|DRo2SmF4bF=0*57HW@x?OXh($aAp86WNS>d^rEmD#3i}jf8Y{vot`u=ya!Z*m zqGR-Y;?8o;4Gm{NIg72#eN18d4#n06$She3h!$c74M;9o4H!i*z<>zC1{Me#Vopr8 zD`Nl%sRKoF96}=i6B<P!NEHc7Pi&&J+lLcyZ*v6z(Og_pC1| zn_pPJf?R%h>EGjRsB$~+=_8|?4Q^CF@aBn{ofE9myQg#h@zwPG;5)qMZmrK8MrO6PEVU!6Gi5$LgB2Rt3I@0tB_!$zU!vV!e75p*n_mf zA-hbeyynY%Ja&JW19r>Z$;6oIkHKyM3xI%rz*Y+|VeIen~i2W>60 z+|K$D;&i}^PqD0R9ekZ)o=aI23sNg7k%8u=)n$td8nex$#mtBmip^fLFd%GI1Qd$QM2i&~%O*oUNC zMoth~rXLBn+r^l2_O5lvR3v^)D6cbK3K;FGL60CHIORyetslF$KK4lcPCc3$AYypU zurMHH7)nqE2r)6EWEg~-NE~)RX%I#hJa$D+BOuwEbifLD8Nee#mi~TETclLyNabIlb4}{ZBp$rtyd_QztL)1 zX-XAp#QmX||IonEkbV!^+vaPJoH`TY;be}~${`AP#%0(N(K1ef&Tm}Oi>!8)Ojbqu z)j1SS+v8e0W>i;&))tO1IHH5PmRzT$; zN^!BzRE-24V+F$N@=Pq@O0S92Qq;TOOOg&{PoG()Qpp%4n?2A=u0^Vt@%T_tJogl1 zJkv`YB@X8uxOC+TFXmGa`|_lSzpgEZRm2NOy-Lc^$#DK?Ch^>Ny~bN+eXiclkrJc6 z5c>QoL#Cu$|7wne{r&SWaZAC$1gXN>fSC@XD!DcwcHl^?U^t;Z8i#nH3s4*&fbd~= z0}3F5AoK!?1Eol;7FZ9%BcK4RfC5(mutI}ich7huk}mvs;=G+Ie^%6AmF+7=^_lG} zGq~ialo;-l{WQ1!;L#5^xcQj-Kt?&Md$;N-P^o&s0cyFiqhWpNO-T z@72qM0(>=PT9NU@zwl49tL`UDj%F~VXY=uL%^JyFY2%dra0(CRokd8j$Y92u+< zNF;*1^H;~Njp6JVn^X?lrRe7BHNWx7rR7DGLy3gyt2(Xrue6Hz>?*dl`?_8H%P+5I HqM!T+FLlTe literal 0 HcmV?d00001 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 ? '
                                                  {uploadButtonText}
                                                  ' : '') + + '{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?'
                                                    {uploadButtonText}
                                                    ':"")+'{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 ? '
                                                      {uploadButtonText}
                                                      ' : '') + + '{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?'
                                                        {uploadButtonText}
                                                        ':"")+'{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 0000000000000000000000000000000000000000..6fba77609ec50f9ab338a90d658e16bb05b8d206 GIT binary patch literal 1688 zcmb`{Z%i9?90u@T|8OmBp{1p?1LC6bjoZDt zkXep>N0_=eva4($!i&&_A= zW;rjn{OxjfHIq1=2=5CoTwNF*91gVyGpm_kvp>8yy0hlS_a|Ji)@kL>dI%ZU?-^tFX>XV|yhM;_kA{Hbj4_?z5yjh^aFnC>zj^ z_o#~Hj9!_H_*k7~=KW86b?g7){QF<}*1#h#Mg;I?KfvJ(Qn0`|`BAdFr+c0K))KhV z*VBp7xc^E2^r^{p+tmzG058~M{V~WRzNN315Fgk94t9hH^W?`#Bm#eA1pn#xrXL)h zI^;Ui`0^Wt{g;NKDbM;@HlU>SeVzx$S zD(zKa4PR~x=t+@VO553Pr?otfml2s$r5>svWlh@%v+uePKj_d>&Sdse`V8u3A-efVY*!3yWCkn;jGN z4p{|BeYkjFBuDAmvz64yq>ELiQR#^<2l$(TJ_(cKNgIz_Nhh1{G-^uptX@&fDfK2t z(V)gap5e@`c^bWergglzjkZ2b7t|ZiD2PIur&VL64kt(#wk%s zfpfc0(7HF(kPybcEeIz;ajkaEet4sf;8~k<7z?FDCO6U`$2I{oVnGgoggOchLhOqN zujBo1p!U0lG3Lc@doPaP9*MBWqJ6csgq&1;(|E<#M=8iVshFbW=dNn!XfpbUcOt5= zE9QQ%mv7-6?w%-TGfT~7?vfl`ue3$U8Hk1(Ar{J5VlbKLUbFb5$<}64dvvPYQY$gZ z-{o1=x%2J~N7H{eLJ-9RB~A-UI6@GPsO9tr(~w0oVGU3e5=_{g(8NjrC^SKdatd>N zZ_ojXLLvZ|&VD*8OgVP*-7I$V!nFk~4-nrk^8fCs{i$`wIdNfSjQ?CDCdQYDd}#4E zzO35eJ}t3O)RgwDxNBFV=}K*%X_Sk;Fy$B%yR;^qxMio+)4z}Q2rbDSRQTqiegkVMdcOh*)yl$90>MUHO; z$`puTM1;T!MiGo+m3fqAYLQvxXtjzut!L|YXxFvO(^-ylmi4qdoVq8Ac4uw>ME>Hx zocsO!zR&Zxvt0|GTSFoe9Yl!B<~B4nTzL0_!{wN~HyJw=vs$d>FO`q~c|5u=8u&$E z`rp$d!y^Wr0TW~4*6`T%v8gYo7Cpac;**Ks+TexJ3tT?e=kZNFn2Ha@)1OMOsjC_P zXgt;*3#<-Ie=|LDb|lgsxqa*Q!-o&i6ZkVfl;k+`^X!>Ro%83;zjs-6o^)Wvce| zZqW)6eWXI%a||6<_iJLECXwi6I=XNFG6NgG=#Yq4NL#MZ@>*%3MR=0z)YW;K6q>plt6OP!bKnhyus2S=!ILt)->zn-N}q+ zGe8?u0Syen(?A(O@=4I~f4jK7GAmuT%f$-Oix-9rsQwGX^R^x=-(%crz4R;MJ}y>l zKeUvUQop1B#KpJUwuV`6zW6EkX(G#{saf9Gt9M%9;sk*8foPYtGYPQrszc3jFH3UMy z10i6WSr7in#T`kBoH?VJ3qz?W<$=Ls>$@?}akN)(zrWgu+foz7K zr7WgvqXtKoTvk)m*p#MLhg=lWtlPn;D^W`$o82DiDtf~%)*$h2M{{gIrKa7a*sRZy zDG)|DRo2SmF4bF=0*57HW@x?OXh($aAp86WNS>d^rEmD#3i}jf8Y{vot`u=ya!Z*m zqGR-Y;?8o;4Gm{NIg72#eN18d4#n06$She3h!$c74M;9o4H!i*z<>zC1{Me#Vopr8 zD`Nl%sRKoF96}=i6B<P!NEHc7Pi&&J+lLcyZ*v6z(Og_pC1| zn_pPJf?R%h>EGjRsB$~+=_8|?4Q^CF@aBn{ofE9myQg#h@zwPG;5)qMZmrK8MrO6PEVU!6Gi5$LgB2Rt3I@0tB_!$zU!vV!e75p*n_mf zA-hbeyynY%Ja&JW19r>Z$;6oIkHKyM3xI%rz*Y+|VeIen~i2W>60 z+|K$D;&i}^PqD0R9ekZ)o=aI23sNg7k%8u=)n$td8nex$#mtBmip^fLFd%GI1Qd$QM2i&~%O*oUNC zMoth~rXLBn+r^l2_O5lvR3v^)D6cbK3K;FGL60CHIORyetslF$KK4lcPCc3$AYypU zurMHH7)nqE2r)6EWEg~-NE~)RX%I#hJa$D+BOuwEbifLD8Nee#mi~TETclLyNabIlb4}{ZBp$rtyd_QztL)1 zX-XAp#QmX||IonEkbV!^+vaPJoH`TY;be}~${`AP#%0(N(K1ef&Tm}Oi>!8)Ojbqu z)j1SS+v8e0W>i;&))tO1IHH5PmRzT$; zN^!BzRE-24V+F$N@=Pq@O0S92Qq;TOOOg&{PoG()Qpp%4n?2A=u0^Vt@%T_tJogl1 zJkv`YB@X8uxOC+TFXmGa`|_lSzpgEZRm2NOy-Lc^$#DK?Ch^>Ny~bN+eXiclkrJc6 z5c>QoL#Cu$|7wne{r&SWaZAC$1gXN>fSC@XD!DcwcHl^?U^t;Z8i#nH3s4*&fbd~= z0}3F5AoK!?1Eol;7FZ9%BcK4RfC5(mutI}ich7huk}mvs;=G+Ie^%6AmF+7=^_lG} zGq~ialo;-l{WQ1!;L#5^xcQj-Kt?&Md$;N-P^o&s0cyFiqhWpNO-T z@72qM0(>=PT9NU@zwl49tL`UDj%F~VXY=uL%^JyFY2%dra0(CRokd8j$Y92u+< zNF;*1^H;~Njp6JVn^X?lrRe7BHNWx7rR7DGLy3gyt2(Xrue6Hz>?*dl`?_8H%P+5I HqM!T+FLlTe literal 0 HcmV?d00001 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 ? '
                                                          {uploadButtonText}
                                                          ' : '') + + '{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?'
                                                            {uploadButtonText}
                                                            ':"")+'{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 ? '
                                                              {uploadButtonText}
                                                              ' : '') + + '{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?'
                                                                {uploadButtonText}
                                                                ':"")+'{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 0000000000000000000000000000000000000000..6fba77609ec50f9ab338a90d658e16bb05b8d206 GIT binary patch literal 1688 zcmb`{Z%i9?90u@T|8OmBp{1p?1LC6bjoZDt zkXep>N0_=eva4($!i&&_A= zW;rjn{OxjfHIq1=2=5CoTwNF*91gVyGpm_kvp>8yy0hlS_a|Ji)@kL>dI%ZU?-^tFX>XV|yhM;_kA{Hbj4_?z5yjh^aFnC>zj^ z_o#~Hj9!_H_*k7~=KW86b?g7){QF<}*1#h#Mg;I?KfvJ(Qn0`|`BAdFr+c0K))KhV z*VBp7xc^E2^r^{p+tmzG058~M{V~WRzNN315Fgk94t9hH^W?`#Bm#eA1pn#xrXL)h zI^;Ui`0^Wt{g;NKDbM;@HlU>SeVzx$S zD(zKa4PR~x=t+@VO553Pr?otfml2s$r5>svWlh@%v+uePKj_d>&Sdse`V8u3A-efVY*!3yWCkn;jGN z4p{|BeYkjFBuDAmvz64yq>ELiQR#^<2l$(TJ_(cKNgIz_Nhh1{G-^uptX@&fDfK2t z(V)gap5e@`c^bWergglzjkZ2b7t|ZiD2PIur&VL64kt(#wk%s zfpfc0(7HF(kPybcEeIz;ajkaEet4sf;8~k<7z?FDCO6U`$2I{oVnGgoggOchLhOqN zujBo1p!U0lG3Lc@doPaP9*MBWqJ6csgq&1;(|E<#M=8iVshFbW=dNn!XfpbUcOt5= zE9QQ%mv7-6?w%-TGfT~7?vfl`ue3$U8Hk1(Ar{J5VlbKLUbFb5$<}64dvvPYQY$gZ z-{o1=x%2J~N7H{eLJ-9RB~A-UI6@GPsO9tr(~w0oVGU3e5=_{g(8NjrC^SKdatd>N zZ_ojXLLvZ|&VD*8OgVP*-7I$V!nFk~4-nrk^8fCs{i$`wIdNfSjQ?CDCdQYDd}#4E zzO35eJ}t3O)RgwDxNBFV=}K*%X_Sk;Fy$B%yR;^qxMio+)4z}Q2rbDSRQTqiegkVMdcOh*)yl$90>MUHO; z$`puTM1;T!MiGo+m3fqAYLQvxXtjzut!L|YXxFvO(^-ylmi4qdoVq8Ac4uw>ME>Hx zocsO!zR&Zxvt0|GTSFoe9Yl!B<~B4nTzL0_!{wN~HyJw=vs$d>FO`q~c|5u=8u&$E z`rp$d!y^Wr0TW~4*6`T%v8gYo7Cpac;**Ks+TexJ3tT?e=kZNFn2Ha@)1OMOsjC_P zXgt;*3#<-Ie=|LDb|lgsxqa*Q!-o&i6ZkVfl;k+`^X!>Ro%83;zjs-6o^)Wvce| zZqW)6eWXI%a||6<_iJLECXwi6I=XNFG6NgG=#Yq4NL#MZ@>*%3MR=0z)YW;K6q>plt6OP!bKnhyus2S=!ILt)->zn-N}q+ zGe8?u0Syen(?A(O@=4I~f4jK7GAmuT%f$-Oix-9rsQwGX^R^x=-(%crz4R;MJ}y>l zKeUvUQop1B#KpJUwuV`6zW6EkX(G#{saf9Gt9M%9;sk*8foPYtGYPQrszc3jFH3UMy z10i6WSr7in#T`kBoH?VJ3qz?W<$=Ls>$@?}akN)(zrWgu+foz7K zr7WgvqXtKoTvk)m*p#MLhg=lWtlPn;D^W`$o82DiDtf~%)*$h2M{{gIrKa7a*sRZy zDG)|DRo2SmF4bF=0*57HW@x?OXh($aAp86WNS>d^rEmD#3i}jf8Y{vot`u=ya!Z*m zqGR-Y;?8o;4Gm{NIg72#eN18d4#n06$She3h!$c74M;9o4H!i*z<>zC1{Me#Vopr8 zD`Nl%sRKoF96}=i6B<P!NEHc7Pi&&J+lLcyZ*v6z(Og_pC1| zn_pPJf?R%h>EGjRsB$~+=_8|?4Q^CF@aBn{ofE9myQg#h@zwPG;5)qMZmrK8MrO6PEVU!6Gi5$LgB2Rt3I@0tB_!$zU!vV!e75p*n_mf zA-hbeyynY%Ja&JW19r>Z$;6oIkHKyM3xI%rz*Y+|VeIen~i2W>60 z+|K$D;&i}^PqD0R9ekZ)o=aI23sNg7k%8u=)n$td8nex$#mtBmip^fLFd%GI1Qd$QM2i&~%O*oUNC zMoth~rXLBn+r^l2_O5lvR3v^)D6cbK3K;FGL60CHIORyetslF$KK4lcPCc3$AYypU zurMHH7)nqE2r)6EWEg~-NE~)RX%I#hJa$D+BOuwEbifLD8Nee#mi~TETclLyNabIlb4}{ZBp$rtyd_QztL)1 zX-XAp#QmX||IonEkbV!^+vaPJoH`TY;be}~${`AP#%0(N(K1ef&Tm}Oi>!8)Ojbqu z)j1SS+v8e0W>i;&))tO1IHH5PmRzT$; zN^!BzRE-24V+F$N@=Pq@O0S92Qq;TOOOg&{PoG()Qpp%4n?2A=u0^Vt@%T_tJogl1 zJkv`YB@X8uxOC+TFXmGa`|_lSzpgEZRm2NOy-Lc^$#DK?Ch^>Ny~bN+eXiclkrJc6 z5c>QoL#Cu$|7wne{r&SWaZAC$1gXN>fSC@XD!DcwcHl^?U^t;Z8i#nH3s4*&fbd~= z0}3F5AoK!?1Eol;7FZ9%BcK4RfC5(mutI}ich7huk}mvs;=G+Ie^%6AmF+7=^_lG} zGq~ialo;-l{WQ1!;L#5^xcQj-Kt?&Md$;N-P^o&s0cyFiqhWpNO-T z@72qM0(>=PT9NU@zwl49tL`UDj%F~VXY=uL%^JyFY2%dra0(CRokd8j$Y92u+< zNF;*1^H;~Njp6JVn^X?lrRe7BHNWx7rR7DGLy3gyt2(Xrue6Hz>?*dl`?_8H%P+5I HqM!T+FLlTe literal 0 HcmV?d00001 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 ? '
                                                                  {uploadButtonText}
                                                                  ' : '') + + '{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?'
                                                                    {uploadButtonText}
                                                                    ':"")+'{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 ? '
                                                                      {uploadButtonText}
                                                                      ' : '') + + '{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?'
                                                                        {uploadButtonText}
                                                                        ':"")+'{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 0000000000000000000000000000000000000000..6fba77609ec50f9ab338a90d658e16bb05b8d206 GIT binary patch literal 1688 zcmb`{Z%i9?90u@T|8OmBp{1p?1LC6bjoZDt zkXep>N0_=eva4($!i&&_A= zW;rjn{OxjfHIq1=2=5CoTwNF*91gVyGpm_kvp>8yy0hlS_a|Ji)@kL>dI%ZU?-^tFX>XV|yhM;_kA{Hbj4_?z5yjh^aFnC>zj^ z_o#~Hj9!_H_*k7~=KW86b?g7){QF<}*1#h#Mg;I?KfvJ(Qn0`|`BAdFr+c0K))KhV z*VBp7xc^E2^r^{p+tmzG058~M{V~WRzNN315Fgk94t9hH^W?`#Bm#eA1pn#xrXL)h zI^;Ui`0^Wt{g;NKDbM;@HlU>SeVzx$S zD(zKa4PR~x=t+@VO553Pr?otfml2s$r5>svWlh@%v+uePKj_d>&Sdse`V8u3A-efVY*!3yWCkn;jGN z4p{|BeYkjFBuDAmvz64yq>ELiQR#^<2l$(TJ_(cKNgIz_Nhh1{G-^uptX@&fDfK2t z(V)gap5e@`c^bWergglzjkZ2b7t|ZiD2PIur&VL64kt(#wk%s zfpfc0(7HF(kPybcEeIz;ajkaEet4sf;8~k<7z?FDCO6U`$2I{oVnGgoggOchLhOqN zujBo1p!U0lG3Lc@doPaP9*MBWqJ6csgq&1;(|E<#M=8iVshFbW=dNn!XfpbUcOt5= zE9QQ%mv7-6?w%-TGfT~7?vfl`ue3$U8Hk1(Ar{J5VlbKLUbFb5$<}64dvvPYQY$gZ z-{o1=x%2J~N7H{eLJ-9RB~A-UI6@GPsO9tr(~w0oVGU3e5=_{g(8NjrC^SKdatd>N zZ_ojXLLvZ|&VD*8OgVP*-7I$V!nFk~4-nrk^8fCs{i$`wIdNfSjQ?CDCdQYDd}#4E zzO35eJ}t3O)RgwDxNBFV=}K*%X_Sk;Fy$B%yR;^qxMio+)4z}Q2rbDSRQTqiegkVMdcOh*)yl$90>MUHO; z$`puTM1;T!MiGo+m3fqAYLQvxXtjzut!L|YXxFvO(^-ylmi4qdoVq8Ac4uw>ME>Hx zocsO!zR&Zxvt0|GTSFoe9Yl!B<~B4nTzL0_!{wN~HyJw=vs$d>FO`q~c|5u=8u&$E z`rp$d!y^Wr0TW~4*6`T%v8gYo7Cpac;**Ks+TexJ3tT?e=kZNFn2Ha@)1OMOsjC_P zXgt;*3#<-Ie=|LDb|lgsxqa*Q!-o&i6ZkVfl;k+`^X!>Ro%83;zjs-6o^)Wvce| zZqW)6eWXI%a||6<_iJLECXwi6I=XNFG6NgG=#Yq4NL#MZ@>*%3MR=0z)YW;K6q>plt6OP!bKnhyus2S=!ILt)->zn-N}q+ zGe8?u0Syen(?A(O@=4I~f4jK7GAmuT%f$-Oix-9rsQwGX^R^x=-(%crz4R;MJ}y>l zKeUvUQop1B#KpJUwuV`6zW6EkX(G#{saf9Gt9M%9;sk*8foPYtGYPQrszc3jFH3UMy z10i6WSr7in#T`kBoH?VJ3qz?W<$=Ls>$@?}akN)(zrWgu+foz7K zr7WgvqXtKoTvk)m*p#MLhg=lWtlPn;D^W`$o82DiDtf~%)*$h2M{{gIrKa7a*sRZy zDG)|DRo2SmF4bF=0*57HW@x?OXh($aAp86WNS>d^rEmD#3i}jf8Y{vot`u=ya!Z*m zqGR-Y;?8o;4Gm{NIg72#eN18d4#n06$She3h!$c74M;9o4H!i*z<>zC1{Me#Vopr8 zD`Nl%sRKoF96}=i6B<P!NEHc7Pi&&J+lLcyZ*v6z(Og_pC1| zn_pPJf?R%h>EGjRsB$~+=_8|?4Q^CF@aBn{ofE9myQg#h@zwPG;5)qMZmrK8MrO6PEVU!6Gi5$LgB2Rt3I@0tB_!$zU!vV!e75p*n_mf zA-hbeyynY%Ja&JW19r>Z$;6oIkHKyM3xI%rz*Y+|VeIen~i2W>60 z+|K$D;&i}^PqD0R9ekZ)o=aI23sNg7k%8u=)n$td8nex$#mtBmip^fLFd%GI1Qd$QM2i&~%O*oUNC zMoth~rXLBn+r^l2_O5lvR3v^)D6cbK3K;FGL60CHIORyetslF$KK4lcPCc3$AYypU zurMHH7)nqE2r)6EWEg~-NE~)RX%I#hJa$D+BOuwEbifLD8Nee#mi~TETclLyNabIlb4}{ZBp$rtyd_QztL)1 zX-XAp#QmX||IonEkbV!^+vaPJoH`TY;be}~${`AP#%0(N(K1ef&Tm}Oi>!8)Ojbqu z)j1SS+v8e0W>i;&))tO1IHH5PmRzT$; zN^!BzRE-24V+F$N@=Pq@O0S92Qq;TOOOg&{PoG()Qpp%4n?2A=u0^Vt@%T_tJogl1 zJkv`YB@X8uxOC+TFXmGa`|_lSzpgEZRm2NOy-Lc^$#DK?Ch^>Ny~bN+eXiclkrJc6 z5c>QoL#Cu$|7wne{r&SWaZAC$1gXN>fSC@XD!DcwcHl^?U^t;Z8i#nH3s4*&fbd~= z0}3F5AoK!?1Eol;7FZ9%BcK4RfC5(mutI}ich7huk}mvs;=G+Ie^%6AmF+7=^_lG} zGq~ialo;-l{WQ1!;L#5^xcQj-Kt?&Md$;N-P^o&s0cyFiqhWpNO-T z@72qM0(>=PT9NU@zwl49tL`UDj%F~VXY=uL%^JyFY2%dra0(CRokd8j$Y92u+< zNF;*1^H;~Njp6JVn^X?lrRe7BHNWx7rR7DGLy3gyt2(Xrue6Hz>?*dl`?_8H%P+5I HqM!T+FLlTe literal 0 HcmV?d00001 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 ? '
                                                                          {uploadButtonText}
                                                                          ' : '') + + '{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?'
                                                                            {uploadButtonText}
                                                                            ':"")+'{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 ? '
                                                                              {uploadButtonText}
                                                                              ' : '') + + '{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?'
                                                                                {uploadButtonText}
                                                                                ':"")+'{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 0000000000000000000000000000000000000000..6fba77609ec50f9ab338a90d658e16bb05b8d206 GIT binary patch literal 1688 zcmb`{Z%i9?90u@T|8OmBp{1p?1LC6bjoZDt zkXep>N0_=eva4($!i&&_A= zW;rjn{OxjfHIq1=2=5CoTwNF*91gVyGpm_kvp>8yy0hlS_a|Ji)@kL>dI%ZU?-^tFX>XV|yhM;_kA{Hbj4_?z5yjh^aFnC>zj^ z_o#~Hj9!_H_*k7~=KW86b?g7){QF<}*1#h#Mg;I?KfvJ(Qn0`|`BAdFr+c0K))KhV z*VBp7xc^E2^r^{p+tmzG058~M{V~WRzNN315Fgk94t9hH^W?`#Bm#eA1pn#xrXL)h zI^;Ui`0^Wt{g;NKDbM;@HlU>SeVzx$S zD(zKa4PR~x=t+@VO553Pr?otfml2s$r5>svWlh@%v+uePKj_d>&Sdse`V8u3A-efVY*!3yWCkn;jGN z4p{|BeYkjFBuDAmvz64yq>ELiQR#^<2l$(TJ_(cKNgIz_Nhh1{G-^uptX@&fDfK2t z(V)gap5e@`c^bWergglzjkZ2b7t|ZiD2PIur&VL64kt(#wk%s zfpfc0(7HF(kPybcEeIz;ajkaEet4sf;8~k<7z?FDCO6U`$2I{oVnGgoggOchLhOqN zujBo1p!U0lG3Lc@doPaP9*MBWqJ6csgq&1;(|E<#M=8iVshFbW=dNn!XfpbUcOt5= zE9QQ%mv7-6?w%-TGfT~7?vfl`ue3$U8Hk1(Ar{J5VlbKLUbFb5$<}64dvvPYQY$gZ z-{o1=x%2J~N7H{eLJ-9RB~A-UI6@GPsO9tr(~w0oVGU3e5=_{g(8NjrC^SKdatd>N zZ_ojXLLvZ|&VD*8OgVP*-7I$V!nFk~4-nrk^8fCs{i$`wIdNfSjQ?CDCdQYDd}#4E zzO35eJ}t3O)RgwDxNBFV=}K*%X_Sk;Fy$B%yR;^qxMio+)4z}Q2rbDSRQTqiegkVMdcOh*)yl$90>MUHO; z$`puTM1;T!MiGo+m3fqAYLQvxXtjzut!L|YXxFvO(^-ylmi4qdoVq8Ac4uw>ME>Hx zocsO!zR&Zxvt0|GTSFoe9Yl!B<~B4nTzL0_!{wN~HyJw=vs$d>FO`q~c|5u=8u&$E z`rp$d!y^Wr0TW~4*6`T%v8gYo7Cpac;**Ks+TexJ3tT?e=kZNFn2Ha@)1OMOsjC_P zXgt;*3#<-Ie=|LDb|lgsxqa*Q!-o&i6ZkVfl;k+`^X!>Ro%83;zjs-6o^)Wvce| zZqW)6eWXI%a||6<_iJLECXwi6I=XNFG6NgG=#Yq4NL#MZ@>*%3MR=0z)YW;K6q>plt6OP!bKnhyus2S=!ILt)->zn-N}q+ zGe8?u0Syen(?A(O@=4I~f4jK7GAmuT%f$-Oix-9rsQwGX^R^x=-(%crz4R;MJ}y>l zKeUvUQop1B#KpJUwuV`6zW6EkX(G#{saf9Gt9M%9;sk*8foPYtGYPQrszc3jFH3UMy z10i6WSr7in#T`kBoH?VJ3qz?W<$=Ls>$@?}akN)(zrWgu+foz7K zr7WgvqXtKoTvk)m*p#MLhg=lWtlPn;D^W`$o82DiDtf~%)*$h2M{{gIrKa7a*sRZy zDG)|DRo2SmF4bF=0*57HW@x?OXh($aAp86WNS>d^rEmD#3i}jf8Y{vot`u=ya!Z*m zqGR-Y;?8o;4Gm{NIg72#eN18d4#n06$She3h!$c74M;9o4H!i*z<>zC1{Me#Vopr8 zD`Nl%sRKoF96}=i6B<P!NEHc7Pi&&J+lLcyZ*v6z(Og_pC1| zn_pPJf?R%h>EGjRsB$~+=_8|?4Q^CF@aBn{ofE9myQg#h@zwPG;5)qMZmrK8MrO6PEVU!6Gi5$LgB2Rt3I@0tB_!$zU!vV!e75p*n_mf zA-hbeyynY%Ja&JW19r>Z$;6oIkHKyM3xI%rz*Y+|VeIen~i2W>60 z+|K$D;&i}^PqD0R9ekZ)o=aI23sNg7k%8u=)n$td8nex$#mtBmip^fLFd%GI1Qd$QM2i&~%O*oUNC zMoth~rXLBn+r^l2_O5lvR3v^)D6cbK3K;FGL60CHIORyetslF$KK4lcPCc3$AYypU zurMHH7)nqE2r)6EWEg~-NE~)RX%I#hJa$D+BOuwEbifLD8Nee#mi~TETclLyNabIlb4}{ZBp$rtyd_QztL)1 zX-XAp#QmX||IonEkbV!^+vaPJoH`TY;be}~${`AP#%0(N(K1ef&Tm}Oi>!8)Ojbqu z)j1SS+v8e0W>i;&))tO1IHH5PmRzT$; zN^!BzRE-24V+F$N@=Pq@O0S92Qq;TOOOg&{PoG()Qpp%4n?2A=u0^Vt@%T_tJogl1 zJkv`YB@X8uxOC+TFXmGa`|_lSzpgEZRm2NOy-Lc^$#DK?Ch^>Ny~bN+eXiclkrJc6 z5c>QoL#Cu$|7wne{r&SWaZAC$1gXN>fSC@XD!DcwcHl^?U^t;Z8i#nH3s4*&fbd~= z0}3F5AoK!?1Eol;7FZ9%BcK4RfC5(mutI}ich7huk}mvs;=G+Ie^%6AmF+7=^_lG} zGq~ialo;-l{WQ1!;L#5^xcQj-Kt?&Md$;N-P^o&s0cyFiqhWpNO-T z@72qM0(>=PT9NU@zwl49tL`UDj%F~VXY=uL%^JyFY2%dra0(CRokd8j$Y92u+< zNF;*1^H;~Njp6JVn^X?lrRe7BHNWx7rR7DGLy3gyt2(Xrue6Hz>?*dl`?_8H%P+5I HqM!T+FLlTe literal 0 HcmV?d00001 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 ? '
                                                                                  {uploadButtonText}
                                                                                  ' : '') + + '{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?'
                                                                                    {uploadButtonText}
                                                                                    ':"")+'{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 ? '
                                                                                      {uploadButtonText}
                                                                                      ' : '') + + '{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?'
                                                                                        {uploadButtonText}
                                                                                        ':"")+'{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 0000000000000000000000000000000000000000..6fba77609ec50f9ab338a90d658e16bb05b8d206 GIT binary patch literal 1688 zcmb`{Z%i9?90u@T|8OmBp{1p?1LC6bjoZDt zkXep>N0_=eva4($!i&&_A= zW;rjn{OxjfHIq1=2=5CoTwNF*91gVyGpm_kvp>8yy0hlS_a|Ji)@kL>dI%ZU?-^tFX>XV|yhM;_kA{Hbj4_?z5yjh^aFnC>zj^ z_o#~Hj9!_H_*k7~=KW86b?g7){QF<}*1#h#Mg;I?KfvJ(Qn0`|`BAdFr+c0K))KhV z*VBp7xc^E2^r^{p+tmzG058~M{V~WRzNN315Fgk94t9hH^W?`#Bm#eA1pn#xrXL)h zI^;Ui`0^Wt{g;NKDbM;@HlU>SeVzx$S zD(zKa4PR~x=t+@VO553Pr?otfml2s$r5>svWlh@%v+uePKj_d>&Sdse`V8u3A-efVY*!3yWCkn;jGN z4p{|BeYkjFBuDAmvz64yq>ELiQR#^<2l$(TJ_(cKNgIz_Nhh1{G-^uptX@&fDfK2t z(V)gap5e@`c^bWergglzjkZ2b7t|ZiD2PIur&VL64kt(#wk%s zfpfc0(7HF(kPybcEeIz;ajkaEet4sf;8~k<7z?FDCO6U`$2I{oVnGgoggOchLhOqN zujBo1p!U0lG3Lc@doPaP9*MBWqJ6csgq&1;(|E<#M=8iVshFbW=dNn!XfpbUcOt5= zE9QQ%mv7-6?w%-TGfT~7?vfl`ue3$U8Hk1(Ar{J5VlbKLUbFb5$<}64dvvPYQY$gZ z-{o1=x%2J~N7H{eLJ-9RB~A-UI6@GPsO9tr(~w0oVGU3e5=_{g(8NjrC^SKdatd>N zZ_ojXLLvZ|&VD*8OgVP*-7I$V!nFk~4-nrk^8fCs{i$`wIdNfSjQ?CDCdQYDd}#4E zzO35eJ}t3O)RgwDxNBFV=}K*%X_Sk;Fy$B%yR;^qxMio+)4z}Q2rbDSRQTqiegkVMdcOh*)yl$90>MUHO; z$`puTM1;T!MiGo+m3fqAYLQvxXtjzut!L|YXxFvO(^-ylmi4qdoVq8Ac4uw>ME>Hx zocsO!zR&Zxvt0|GTSFoe9Yl!B<~B4nTzL0_!{wN~HyJw=vs$d>FO`q~c|5u=8u&$E z`rp$d!y^Wr0TW~4*6`T%v8gYo7Cpac;**Ks+TexJ3tT?e=kZNFn2Ha@)1OMOsjC_P zXgt;*3#<-Ie=|LDb|lgsxqa*Q!-o&i6ZkVfl;k+`^X!>Ro%83;zjs-6o^)Wvce| zZqW)6eWXI%a||6<_iJLECXwi6I=XNFG6NgG=#Yq4NL#MZ@>*%3MR=0z)YW;K6q>plt6OP!bKnhyus2S=!ILt)->zn-N}q+ zGe8?u0Syen(?A(O@=4I~f4jK7GAmuT%f$-Oix-9rsQwGX^R^x=-(%crz4R;MJ}y>l zKeUvUQop1B#KpJUwuV`6zW6EkX(G#{saf9Gt9M%9;sk*8foPYtGYPQrszc3jFH3UMy z10i6WSr7in#T`kBoH?VJ3qz?W<$=Ls>$@?}akN)(zrWgu+foz7K zr7WgvqXtKoTvk)m*p#MLhg=lWtlPn;D^W`$o82DiDtf~%)*$h2M{{gIrKa7a*sRZy zDG)|DRo2SmF4bF=0*57HW@x?OXh($aAp86WNS>d^rEmD#3i}jf8Y{vot`u=ya!Z*m zqGR-Y;?8o;4Gm{NIg72#eN18d4#n06$She3h!$c74M;9o4H!i*z<>zC1{Me#Vopr8 zD`Nl%sRKoF96}=i6B<P!NEHc7Pi&&J+lLcyZ*v6z(Og_pC1| zn_pPJf?R%h>EGjRsB$~+=_8|?4Q^CF@aBn{ofE9myQg#h@zwPG;5)qMZmrK8MrO6PEVU!6Gi5$LgB2Rt3I@0tB_!$zU!vV!e75p*n_mf zA-hbeyynY%Ja&JW19r>Z$;6oIkHKyM3xI%rz*Y+|VeIen~i2W>60 z+|K$D;&i}^PqD0R9ekZ)o=aI23sNg7k%8u=)n$td8nex$#mtBmip^fLFd%GI1Qd$QM2i&~%O*oUNC zMoth~rXLBn+r^l2_O5lvR3v^)D6cbK3K;FGL60CHIORyetslF$KK4lcPCc3$AYypU zurMHH7)nqE2r)6EWEg~-NE~)RX%I#hJa$D+BOuwEbifLD8Nee#mi~TETclLyNabIlb4}{ZBp$rtyd_QztL)1 zX-XAp#QmX||IonEkbV!^+vaPJoH`TY;be}~${`AP#%0(N(K1ef&Tm}Oi>!8)Ojbqu z)j1SS+v8e0W>i;&))tO1IHH5PmRzT$; zN^!BzRE-24V+F$N@=Pq@O0S92Qq;TOOOg&{PoG()Qpp%4n?2A=u0^Vt@%T_tJogl1 zJkv`YB@X8uxOC+TFXmGa`|_lSzpgEZRm2NOy-Lc^$#DK?Ch^>Ny~bN+eXiclkrJc6 z5c>QoL#Cu$|7wne{r&SWaZAC$1gXN>fSC@XD!DcwcHl^?U^t;Z8i#nH3s4*&fbd~= z0}3F5AoK!?1Eol;7FZ9%BcK4RfC5(mutI}ich7huk}mvs;=G+Ie^%6AmF+7=^_lG} zGq~ialo;-l{WQ1!;L#5^xcQj-Kt?&Md$;N-P^o&s0cyFiqhWpNO-T z@72qM0(>=PT9NU@zwl49tL`UDj%F~VXY=uL%^JyFY2%dra0(CRokd8j$Y92u+< zNF;*1^H;~Njp6JVn^X?lrRe7BHNWx7rR7DGLy3gyt2(Xrue6Hz>?*dl`?_8H%P+5I HqM!T+FLlTe literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..13aa89a3463729ae6987f84f65afae13d07dcf37 GIT binary patch literal 145 zcmZ?wbhEHb7VX+#JX#3zK3=Gx)NBT74 literal 0 HcmV?d00001 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 ? '
                                                                                          {uploadButtonText}
                                                                                          ' : '') + + '{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?"":'
                                                                                            {uploadButtonText}
                                                                                            ')+'{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 ? '
                                                                                              {uploadButtonText}
                                                                                              ' : '') + + '{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?"":'
                                                                                                {uploadButtonText}
                                                                                                ')+'{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 0000000000000000000000000000000000000000..6fba77609ec50f9ab338a90d658e16bb05b8d206 GIT binary patch literal 1688 zcmb`{Z%i9?90u@T|8OmBp{1p?1LC6bjoZDt zkXep>N0_=eva4($!i&&_A= zW;rjn{OxjfHIq1=2=5CoTwNF*91gVyGpm_kvp>8yy0hlS_a|Ji)@kL>dI%ZU?-^tFX>XV|yhM;_kA{Hbj4_?z5yjh^aFnC>zj^ z_o#~Hj9!_H_*k7~=KW86b?g7){QF<}*1#h#Mg;I?KfvJ(Qn0`|`BAdFr+c0K))KhV z*VBp7xc^E2^r^{p+tmzG058~M{V~WRzNN315Fgk94t9hH^W?`#Bm#eA1pn#xrXL)h zI^;Ui`0^Wt{g;NKDbM;@HlU>SeVzx$S zD(zKa4PR~x=t+@VO553Pr?otfml2s$r5>svWlh@%v+uePKj_d>&Sdse`V8u3A-efVY*!3yWCkn;jGN z4p{|BeYkjFBuDAmvz64yq>ELiQR#^<2l$(TJ_(cKNgIz_Nhh1{G-^uptX@&fDfK2t z(V)gap5e@`c^bWergglzjkZ2b7t|ZiD2PIur&VL64kt(#wk%s zfpfc0(7HF(kPybcEeIz;ajkaEet4sf;8~k<7z?FDCO6U`$2I{oVnGgoggOchLhOqN zujBo1p!U0lG3Lc@doPaP9*MBWqJ6csgq&1;(|E<#M=8iVshFbW=dNn!XfpbUcOt5= zE9QQ%mv7-6?w%-TGfT~7?vfl`ue3$U8Hk1(Ar{J5VlbKLUbFb5$<}64dvvPYQY$gZ z-{o1=x%2J~N7H{eLJ-9RB~A-UI6@GPsO9tr(~w0oVGU3e5=_{g(8NjrC^SKdatd>N zZ_ojXLLvZ|&VD*8OgVP*-7I$V!nFk~4-nrk^8fCs{i$`wIdNfSjQ?CDCdQYDd}#4E zzO35eJ}t3O)RgwDxNBFV=}K*%X_Sk;Fy$B%yR;^qxMio+)4z}Q2rbDSRQTqiegkVMdcOh*)yl$90>MUHO; z$`puTM1;T!MiGo+m3fqAYLQvxXtjzut!L|YXxFvO(^-ylmi4qdoVq8Ac4uw>ME>Hx zocsO!zR&Zxvt0|GTSFoe9Yl!B<~B4nTzL0_!{wN~HyJw=vs$d>FO`q~c|5u=8u&$E z`rp$d!y^Wr0TW~4*6`T%v8gYo7Cpac;**Ks+TexJ3tT?e=kZNFn2Ha@)1OMOsjC_P zXgt;*3#<-Ie=|LDb|lgsxqa*Q!-o&i6ZkVfl;k+`^X!>Ro%83;zjs-6o^)Wvce| zZqW)6eWXI%a||6<_iJLECXwi6I=XNFG6NgG=#Yq4NL#MZ@>*%3MR=0z)YW;K6q>plt6OP!bKnhyus2S=!ILt)->zn-N}q+ zGe8?u0Syen(?A(O@=4I~f4jK7GAmuT%f$-Oix-9rsQwGX^R^x=-(%crz4R;MJ}y>l zKeUvUQop1B#KpJUwuV`6zW6EkX(G#{saf9Gt9M%9;sk*8foPYtGYPQrszc3jFH3UMy z10i6WSr7in#T`kBoH?VJ3qz?W<$=Ls>$@?}akN)(zrWgu+foz7K zr7WgvqXtKoTvk)m*p#MLhg=lWtlPn;D^W`$o82DiDtf~%)*$h2M{{gIrKa7a*sRZy zDG)|DRo2SmF4bF=0*57HW@x?OXh($aAp86WNS>d^rEmD#3i}jf8Y{vot`u=ya!Z*m zqGR-Y;?8o;4Gm{NIg72#eN18d4#n06$She3h!$c74M;9o4H!i*z<>zC1{Me#Vopr8 zD`Nl%sRKoF96}=i6B<P!NEHc7Pi&&J+lLcyZ*v6z(Og_pC1| zn_pPJf?R%h>EGjRsB$~+=_8|?4Q^CF@aBn{ofE9myQg#h@zwPG;5)qMZmrK8MrO6PEVU!6Gi5$LgB2Rt3I@0tB_!$zU!vV!e75p*n_mf zA-hbeyynY%Ja&JW19r>Z$;6oIkHKyM3xI%rz*Y+|VeIen~i2W>60 z+|K$D;&i}^PqD0R9ekZ)o=aI23sNg7k%8u=)n$td8nex$#mtBmip^fLFd%GI1Qd$QM2i&~%O*oUNC zMoth~rXLBn+r^l2_O5lvR3v^)D6cbK3K;FGL60CHIORyetslF$KK4lcPCc3$AYypU zurMHH7)nqE2r)6EWEg~-NE~)RX%I#hJa$D+BOuwEbifLD8Nee#mi~TETclLyNabIlb4}{ZBp$rtyd_QztL)1 zX-XAp#QmX||IonEkbV!^+vaPJoH`TY;be}~${`AP#%0(N(K1ef&Tm}Oi>!8)Ojbqu z)j1SS+v8e0W>i;&))tO1IHH5PmRzT$; zN^!BzRE-24V+F$N@=Pq@O0S92Qq;TOOOg&{PoG()Qpp%4n?2A=u0^Vt@%T_tJogl1 zJkv`YB@X8uxOC+TFXmGa`|_lSzpgEZRm2NOy-Lc^$#DK?Ch^>Ny~bN+eXiclkrJc6 z5c>QoL#Cu$|7wne{r&SWaZAC$1gXN>fSC@XD!DcwcHl^?U^t;Z8i#nH3s4*&fbd~= z0}3F5AoK!?1Eol;7FZ9%BcK4RfC5(mutI}ich7huk}mvs;=G+Ie^%6AmF+7=^_lG} zGq~ialo;-l{WQ1!;L#5^xcQj-Kt?&Md$;N-P^o&s0cyFiqhWpNO-T z@72qM0(>=PT9NU@zwl49tL`UDj%F~VXY=uL%^JyFY2%dra0(CRokd8j$Y92u+< zNF;*1^H;~Njp6JVn^X?lrRe7BHNWx7rR7DGLy3gyt2(Xrue6Hz>?*dl`?_8H%P+5I HqM!T+FLlTe literal 0 HcmV?d00001 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": [