diff options
author | Sem <sembrestels@riseup.net> | 2012-08-02 21:44:44 +0200 |
---|---|---|
committer | Sem <sembrestels@riseup.net> | 2012-08-02 21:44:44 +0200 |
commit | dad0209bbd7994b623bc0126e3e37b1f521d2977 (patch) | |
tree | 1e954b32439cbfc2acbbd63ed62dc372a260f37e | |
parent | fa81c487488064f684649272a6579079d3a88e2c (diff) | |
parent | 28b1669c4b1afaed7429da2cc9580340dcb13b6e (diff) | |
download | elgg-dad0209bbd7994b623bc0126e3e37b1f521d2977.tar.gz elgg-dad0209bbd7994b623bc0126e3e37b1f521d2977.tar.bz2 |
Merge branch 'jquery-uploader' of git://github.com/sembrestels/Tidypics
Conflicts:
start.php
views/default/photos/css.php
47 files changed, 6676 insertions, 33 deletions
diff --git a/languages/en.php b/languages/en.php index 32174e278..65a7ea417 100644 --- a/languages/en.php +++ b/languages/en.php @@ -57,6 +57,13 @@ $english = array( 'tidypics:sort' => 'Sorting the %s album', 'tidypics:none' => 'No photo albums', + //upload + 'tidypics:upload:error' => 'Error:', + 'tidypics:upload:maxfilesize' => 'File is too big', + 'tidypics:upload:minfilesize' => 'File is too small', + 'tidypics:upload:acceptfiletypes' => 'Filetype not allowed', + 'tidypics:upload:maxnumberoffiles' => 'Max number of files exceeded', + //settings 'tidypics:settings' => 'Settings', 'tidypics:settings:main' => 'Primary settings', @@ -155,8 +162,7 @@ $english = array( 'tidypics:uploader:upload' => "Upload photos", 'tidypics:uploader:describe' => "Describe photos", 'tidypics:uploader:filedesc' => 'Image files (jpeg, png, gif)', - 'tidypics:uploader:instructs' => 'There are three easy steps for adding photos to your album using this uploader: choosing, uploading, and describing them. There is a %s MB maximum per photo. If you do not have Flash, there is also a <a href="%s">basic uploader</a> available.', - 'tidypics:uploader:basic' => 'You can upload up to 10 photos at a time (%s MB maximum per photo)', + 'tidypics:uploader:help' => 'Tip: use <code>Ctrl</code> or <code>Shift</code> keys to select more than one file. You also can drag&drop photos from desktop.', 'tidypics:sort:instruct' => 'Sort the album photos by dragging and dropping the images. Then click the save button.', 'tidypics:sort:no_images' => 'No images found to sort. Upload images using the link above.', diff --git a/pages/photos/image/upload.php b/pages/photos/image/upload.php index 61be8523b..c8e57038c 100644 --- a/pages/photos/image/upload.php +++ b/pages/photos/image/upload.php @@ -38,7 +38,21 @@ elgg_push_breadcrumb($owner->name, "photos/owner/$owner->username"); elgg_push_breadcrumb($album->getTitle(), $album->getURL()); elgg_push_breadcrumb(elgg_echo('album:addpix')); -$content = elgg_view('forms/photos/basic_upload', array('entity' => $album)); +// load javascript dependences +elgg_load_js('jquery-tmpl'); +elgg_load_js('jquery-load-image'); +elgg_load_js('jquery-canvas-to-blob'); +elgg_load_js('jquery-fileupload'); +elgg_load_js('jquery-fileupload-ui'); +elgg_load_js('tidypics:upload'); + +$form_vars = array( + 'id' => 'fileupload', + 'action' => 'action/photos/image/upload', + 'enctype' => 'multipart/form-data', +); + +$content = elgg_view_form('photos/basic_upload', $form_vars, array('entity' => $album)); $body = elgg_view_layout('content', array( 'content' => $content, @@ -39,6 +39,19 @@ function tidypics_init() { elgg_register_simplecache_view('js/photos/tidypics'); elgg_register_js('tidypics', $js, 'footer'); + $js_base = 'mod/lightpics/vendors/jquery-file-upload/js'; + + elgg_register_js('jquery-tmpl', "http://blueimp.github.com/JavaScript-Templates/tmpl.js", 'footer'); + elgg_register_js('jquery-load-image', "$js_base/vendor/load-image.min.js", 'footer'); + elgg_register_js('jquery-canvas-to-blob', "$js_base/vendor/canvas-to-blob.min.js", 'footer'); + elgg_register_js('jquery-image-gallery', "$js_base/vendor/jquery.image-gallery.min.js", 'footer'); + elgg_register_js('jquery-iframe-transport', "$js_base/jquery.iframe-transport.js", 'footer'); + + elgg_register_js('jquery-fileupload', "$js_base/jquery.fileupload.js", 'footer'); + elgg_register_js('jquery-fileupload-fp', "$js_base/jquery.fileupload-fp.js", 'footer'); + elgg_register_js('jquery-fileupload-ui', "$js_base/jquery.fileupload-ui.js", 'footer'); + elgg_register_js('jquery-fileupload-jui', "$js_base/jquery.fileupload-jui.js", 'footer'); + // Add photos link to owner block/hover menus elgg_register_plugin_hook_handler('register', 'menu:owner_block', 'tidypics_owner_block_menu'); diff --git a/vendors/jquery-file-upload/README.md b/vendors/jquery-file-upload/README.md new file mode 100644 index 000000000..4bdca2d5c --- /dev/null +++ b/vendors/jquery-file-upload/README.md @@ -0,0 +1,73 @@ +# jQuery File Upload Plugin + +## Demo +[Demo File Upload](http://blueimp.github.com/jQuery-File-Upload/) + +## Setup instructions +* [How to setup the plugin on your website](https://github.com/blueimp/jQuery-File-Upload/wiki/Setup) +* [How to use only the basic plugin (minimal setup guide).](https://github.com/blueimp/jQuery-File-Upload/wiki/Basic-plugin) + +## Features +* **Multiple file upload:** + Allows to select multiple files at once and upload them simultaneously. +* **Drag & Drop support:** + Allows to upload files by dragging them from your desktop or filemanager and dropping them on your browser window. +* **Upload progress bar:** + Shows a progress bar indicating the upload progress for individual files and for all uploads combined. +* **Cancelable uploads:** + Individual file uploads can be canceled to stop the upload progress. +* **Resumable uploads:** + Aborted uploads can be resumed with browsers supporting the Blob API. +* **Chunked uploads:** + Large files can be uploaded in smaller chunks with browsers supporting the Blob API. +* **Client-side image resizing:** + Images can be automatically resized on client-side with browsers supporting the required JS APIs. +* **Preview images:** + A preview of image files can be displayed before uploading with browsers supporting the required JS APIs. +* **No browser plugins (e.g. Adobe Flash) required:** + The implementation is based on open standards like HTML5 and JavaScript and requires no additional browser plugins. +* **Graceful fallback for legacy browsers:** + Uploads files via XMLHttpRequests if supported and uses iframes as fallback for legacy browsers. +* **HTML file upload form fallback:** + Shows a standard HTML file upload form if JavaScript is disabled. +* **Cross-site file uploads:** + Supports uploading files to a different domain with Cross-site XMLHttpRequests. +* **Multiple plugin instances:** + Allows to use multiple plugin instances on the same webpage. +* **Customizable and extensible:** + Provides an API to set individual options and define callBack methods for various upload events. +* **Multipart and file contents stream uploads:** + Files can be uploaded as standard "multipart/form-data" or file contents stream (HTTP PUT file upload). +* **Compatible with any server-side application platform:** + Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads. + +## Requirements +* [jQuery](http://jquery.com/) v. 1.6+ +* [jQuery UI](http://jqueryui.com/) v. 1.8+ +* [jQuery Iframe Transport plugin](https://github.com/blueimp/jQuery-File-Upload/blob/master/jquery.iframe-transport.js) (included) +* [JavaScript Templates engine](https://github.com/blueimp/JavaScript-Templates) v. 2.1.0+ (optional) +* [JavaScript Load Image function](https://github.com/blueimp/JavaScript-Load-Image) v. 1.1.6+ (optional) +* [JavaScript Canvas to Blob function](https://github.com/blueimp/JavaScript-Canvas-to-Blob) v. 2.0.0+ (optional) + +The jQuery Iframe Transport is required for [browsers without XHR file upload support](https://github.com/blueimp/jQuery-File-Upload/wiki/Browser-support). +The UI version of the File Upload plugin also requires the JavaScript Templates engine as well as the JavaScript Load Image and JavaScript Canvas to Blob functions (for the image previews and resizing functionality). These dependencies are marked as optional, as the basic File Upload plugin can be used without them and the UI version of the plugin can be extended to override these dependencies with alternative solutions. + +The User Interface is built with [jQuery UI](http://jqueryui.com/). The demo also includes the [jQuery Image Gallery Plugin](https://github.com/blueimp/jQuery-Image-Gallery). + +The repository also includes the [jQuery XDomainRequest Transport plugin](https://github.com/blueimp/jQuery-File-Upload/blob/master/js/cors/jquery.xdr-transport.js), which enables Cross-domain AJAX requests (GET and POST only) in Microsoft Internet Explorer >= 8. However, the XDomainRequest object doesn't support file uploads and the plugin is only used by the [Demo](http://blueimp.github.com/jQuery-File-Upload/) for Cross-domain requests to delete uploaded files from the demo file upload service. + +[Cross-domain File Uploads](https://github.com/blueimp/jQuery-File-Upload/wiki/Cross-domain-uploads) using the [Iframe Transport plugin](https://github.com/blueimp/jQuery-File-Upload/blob/master/js/jquery.iframe-transport.js) require a redirect back to the origin server to retrieve the upload results. The [example implementation](https://github.com/blueimp/jQuery-File-Upload/blob/master/js/main.js) makes use of [result.html](https://github.com/blueimp/jQuery-File-Upload/blob/master/cors/result.html) as a static redirect page for the origin server. + +## Browser Support (tested versions) +* Google Chrome - 7.0+ +* Apple Safari - 4.0+ +* Mozilla Firefox - 3.0+ +* Opera - 10.0+ +* Microsoft Internet Explorer 6.0+ + +Drag & Drop is only supported on Google Chrome, Firefox 4.0+, Safari 5.0+ and Opera 12.0+. +Microsoft Internet Explorer has no support for multiple file selection or upload progress. +[Extended browser support information](https://github.com/blueimp/jQuery-File-Upload/wiki/Browser-support). + +## License +Released under the [MIT license](http://www.opensource.org/licenses/MIT). diff --git a/vendors/jquery-file-upload/cors/postmessage.html b/vendors/jquery-file-upload/cors/postmessage.html new file mode 100644 index 000000000..4a93bb867 --- /dev/null +++ b/vendors/jquery-file-upload/cors/postmessage.html @@ -0,0 +1,75 @@ +<!DOCTYPE HTML> +<!-- +/* + * jQuery File Upload Plugin postMessage API 1.1.1 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ +--> +<html lang="en"> +<head> +<meta charset="utf-8"> +<title>jQuery File Upload Plugin postMessage API</title> +<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> +</head> +<body> +<script> +/*jslint unparam: true, regexp: true */ +/*global $, Blob, FormData, location */ +'use strict'; +var origin = /^http:\/\/example.org/, + target = new RegExp('^(http(s)?:)?\\/\\/' + location.host + '\\/'); +$(window).on('message', function (e) { + e = e.originalEvent; + var s = e.data, + xhr = $.ajaxSettings.xhr(), + f; + if (!origin.test(e.origin)) { + throw new Error('Origin "' + e.origin + '" does not match ' + origin); + } + if (!target.test(e.data.url)) { + throw new Error('Target "' + e.data.url + '" does not match ' + target); + } + $(xhr.upload).on('progress', function (ev) { + ev = ev.originalEvent; + e.source.postMessage({ + id: s.id, + type: ev.type, + timeStamp: ev.timeStamp, + lengthComputable: ev.lengthComputable, + loaded: ev.loaded, + total: ev.total + }, e.origin); + }); + s.xhr = function () { + return xhr; + }; + if (!(s.data instanceof Blob)) { + f = new FormData(); + $.each(s.data, function (i, v) { + f.append(v.name, v.value); + }); + s.data = f; + } + $.ajax(s).always(function (result, statusText, jqXHR) { + if (!jqXHR.done) { + jqXHR = result; + result = null; + } + e.source.postMessage({ + id: s.id, + status: jqXHR.status, + statusText: statusText, + result: result, + headers: jqXHR.getAllResponseHeaders() + }, e.origin); + }); +}); +</script> +</body> +</html>
\ No newline at end of file diff --git a/vendors/jquery-file-upload/cors/result.html b/vendors/jquery-file-upload/cors/result.html new file mode 100644 index 000000000..7c9802684 --- /dev/null +++ b/vendors/jquery-file-upload/cors/result.html @@ -0,0 +1,20 @@ +<!DOCTYPE HTML> +<!-- +/* + * jQuery Iframe Transport Plugin Redirect Page 2.0 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ +--> +<html lang="en"> +<head> +<meta charset="utf-8"> +<title>jQuery Iframe Transport Plugin Redirect Page</title> +</head> +<body><script>document.body.innerHTML=decodeURIComponent(window.location.search.slice(1));</script></body> +</html>
\ No newline at end of file diff --git a/vendors/jquery-file-upload/css/jquery.fileupload-ui.css b/vendors/jquery-file-upload/css/jquery.fileupload-ui.css new file mode 100644 index 000000000..bb706ff06 --- /dev/null +++ b/vendors/jquery-file-upload/css/jquery.fileupload-ui.css @@ -0,0 +1,107 @@ +@charset 'UTF-8'; +/* + * jQuery File Upload UI Plugin CSS 6.3 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +.fileinput-button { + position: relative; + overflow: hidden; + float: left; + margin-right: 5px; +} +.fileinput-button input { + position: absolute; + top: 0; + right: 0; + margin: 0; + border: solid transparent; + border-width: 0 0 100px 200px; + opacity: 0; + filter: alpha(opacity=0); + -moz-transform: translate(-300px, 0) scale(4); + direction: ltr; + cursor: pointer; +} +.fileupload-buttonbar .ui-button, +.fileupload-buttonbar .toggle { + margin-bottom: 5px; +} +.files .progress { + width: 200px; +} +.files .progress, +.fileupload-buttonbar .progress { + height: 20px; +} +.files .ui-progressbar-value, +.fileupload-buttonbar .ui-progressbar-value { + background: url(../img/progressbar.gif); +} +.fileupload-buttonbar .fade, +.files .fade { + display: none; +} +.fileupload-loading { + position: absolute; + left: 50%; + width: 128px; + height: 128px; + background: url(../img/loading.gif) center no-repeat; + display: none; +} +.fileupload-processing .fileupload-loading { + display: block; +} + +/* Fix for IE 6: */ +*html .fileinput-button { + margin-right: -2px; +} +*html .fileinput-button .ui-button-text { + line-height: 24px; +} +*html .fileupload-buttonbar .ui-button { + margin-left: 3px; +} + +/* Fix for IE 7: */ +*+html .fileinput-button { + margin-right: 1px; +} +*+html .fileinput-button .ui-button-text { + line-height: 24px; +} +*+html .fileupload-buttonbar .ui-button { + margin-left: 3px; +} + +@media (max-width: 480px) { + .files .preview * { + width: 40px; + } + .files .name * { + width: 80px; + display: inline-block; + word-wrap: break-word; + } + .files .progress { + width: 20px; + } + .files .delete { + width: 60px; + } +} + +/* Fix for Webkit (Safari, Chrome) */ +@media screen and (-webkit-min-device-pixel-ratio:0) { + .fileinput-button { + margin-top: 2px; + } +} diff --git a/vendors/jquery-file-upload/css/style.css b/vendors/jquery-file-upload/css/style.css new file mode 100644 index 000000000..6ed82e679 --- /dev/null +++ b/vendors/jquery-file-upload/css/style.css @@ -0,0 +1,92 @@ +@charset 'UTF-8'; +/* + * jQuery File Upload Plugin CSS Example 1.1 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2012, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 13px; + line-height: 18px; + width: 960px; + margin: 10px auto; +} +a { + color: #08C; + text-decoration: none; +} +a:hover, +.active a { + color: #005580; + text-decoration: underline; +} +td { + padding: 5px; +} +img { + border: 0; +} +.nav-collapse, +ul.nav, +ul.nav li { + display: inline-block; + margin-right: 10px; +} +.navbar .brand { + font-size: 16px; +} +.row { + zoom: 1; +} +.row:before, .row:after { + display: table; + content: ""; +} +.row:after { + clear: both; +} +.span5 { + width: 400px; + float: left; +} +.span7 { + width: 560px ; + float: left; +} + +/* Fix for IE 6: */ +*html .nav-collapse, +*html ul.nav, +*html ul.nav li { + display: inline; +} +*html .navbar-fixed-top { + margin-bottom: 35px; +} + +/* Fix for IE 7: */ +*+html .nav-collapse, +*+html ul.nav, +*+html ul.nav li { + display: inline; +} +*+html .navbar-fixed-top { + margin-bottom: 35px; +} + +@media (max-width: 980px) { + body { + width: auto; + padding: 10px; + } + .span5, + .span7 { + width: auto; + } +} diff --git a/vendors/jquery-file-upload/img/loading.gif b/vendors/jquery-file-upload/img/loading.gif Binary files differnew file mode 100644 index 000000000..90f28cbdb --- /dev/null +++ b/vendors/jquery-file-upload/img/loading.gif diff --git a/vendors/jquery-file-upload/img/progressbar.gif b/vendors/jquery-file-upload/img/progressbar.gif Binary files differnew file mode 100644 index 000000000..fbcce6bc9 --- /dev/null +++ b/vendors/jquery-file-upload/img/progressbar.gif diff --git a/vendors/jquery-file-upload/index.html b/vendors/jquery-file-upload/index.html new file mode 100644 index 000000000..a57d6fa55 --- /dev/null +++ b/vendors/jquery-file-upload/index.html @@ -0,0 +1,234 @@ +<!DOCTYPE HTML> +<!-- +/* + * jQuery File Upload Plugin Demo 6.9.1 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ +--> +<html lang="en"> +<head> +<!-- Force latest IE rendering engine or ChromeFrame if installed --> +<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><![endif]--> +<meta charset="utf-8"> +<title>jQuery File Upload Demo</title> +<meta name="description" content="File Upload widget with multiple file selection, drag&drop support, progress bar and preview images for jQuery. Supports cross-domain, chunked and resumable file uploads. Works with any server-side platform (Google App Engine, PHP, Python, Ruby on Rails, Java, etc.) that supports standard HTML form file uploads."> +<meta name="viewport" content="width=device-width"> +<!-- jQuery UI styles --> +<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/base/jquery-ui.css" id="theme"> +<!-- jQuery Image Gallery styles --> +<link rel="stylesheet" href="http://blueimp.github.com/jQuery-Image-Gallery/css/jquery.image-gallery.min.css"> +<!-- CSS to style the file input field as button and adjust the jQuery UI progress bars --> +<link rel="stylesheet" href="css/jquery.fileupload-ui.css"> +<!-- Generic page styles --> +<link rel="stylesheet" href="css/style.css"> +<!-- Shim to make HTML5 elements usable in older Internet Explorer versions --> +<!--[if lt IE 9]><script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]--> +</head> +<body> +<div class="navbar navbar-fixed-top"> + <div class="navbar-inner"> + <div class="container"> + <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </a> + <a class="brand" href="https://github.com/blueimp/jQuery-File-Upload">jQuery File Upload</a> + <div class="nav-collapse"> + <ul class="nav"> + <li class="active"><a href="#">Demo</a></li> + <li><a href="https://github.com/blueimp/jQuery-File-Upload/downloads">Downloads</a></li> + <li><a href="https://github.com/blueimp/jQuery-File-Upload">Source Code</a></li> + <li><a href="https://github.com/blueimp/jQuery-File-Upload/wiki">Documentation</a></li> + <li><a href="https://github.com/blueimp/jQuery-File-Upload/issues">Issues</a></li> + <li><a href="test/">Test</a></li> + <li><a href="https://blueimp.net">© Sebastian Tschan</a></li> + </ul> + <select id="theme-switcher" class="pull-right"> + <option value="base" selected>Base</option> + <option value="black-tie">Black Tie</option> + <option value="blitzer">Blitzer</option> + <option value="cupertino">Cupertino</option> + <option value="dark-hive">Dark Hive</option> + <option value="dot-luv">Dot Luv</option> + <option value="eggplant">Eggplant</option> + <option value="excite-bike">Excite Bike</option> + <option value="flick">Flick</option> + <option value="hot-sneaks">Hot sneaks</option> + <option value="humanity">Humanity</option> + <option value="le-frog">Le Frog</option> + <option value="mint-choc">Mint Choc</option> + <option value="overcast">Overcast</option> + <option value="pepper-grinder">Pepper Grinder</option> + <option value="redmond">Redmond</option> + <option value="smoothness">Smoothness</option> + <option value="south-street">South Street</option> + <option value="start">Start</option> + <option value="sunny">Sunny</option> + <option value="swanky-purse">Swanky Purse</option> + <option value="trontastic">Trontastic</option> + <option value="ui-darkness">UI Darkness</option> + <option value="ui-lightness">UI Lightness</option> + <option value="vader">Vader</option> + </select> + </div> + </div> + </div> +</div> +<div class="container"> + <div class="page-header"> + <h1>jQuery File Upload Demo</h1> + </div> + <blockquote> + <p>File Upload widget with multiple file selection, drag&drop support, progress bars and preview images for jQuery.<br> + Supports cross-domain, chunked and resumable file uploads and client-side image resizing.<br> + Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads.</p> + </blockquote> + <br> + <!-- The file upload form used as target for the file upload widget --> + <form id="fileupload" action="server/php/" method="POST" enctype="multipart/form-data"> + <!-- The fileupload-buttonbar contains buttons to add/delete files and start/cancel the upload --> + <div class="row fileupload-buttonbar"> + <div class="span7"> + <!-- The fileinput-button span is used to style the file input field as button --> + <span class="btn btn-success fileinput-button"> + <i class="icon-plus icon-white"></i> + <span>Add files...</span> + <input type="file" name="files[]" multiple> + </span> + <button type="submit" class="btn btn-primary start"> + <i class="icon-upload icon-white"></i> + <span>Start upload</span> + </button> + <button type="reset" class="btn btn-warning cancel"> + <i class="icon-ban-circle icon-white"></i> + <span>Cancel upload</span> + </button> + <button type="button" class="btn btn-danger delete"> + <i class="icon-trash icon-white"></i> + <span>Delete</span> + </button> + <input type="checkbox" class="toggle"> + </div> + <!-- The global progress information --> + <div class="span5 fileupload-progress fade"> + <!-- The global progress bar --> + <div class="progress progress-success progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100"> + <div class="bar" style="width:0%;"></div> + </div> + <!-- The extended global progress information --> + <div class="progress-extended"> </div> + </div> + </div> + <!-- The loading indicator is shown during file processing --> + <div class="fileupload-loading"></div> + <br> + <!-- The table listing the files available for upload/download --> + <table role="presentation" class="table table-striped"><tbody class="files" data-toggle="modal-gallery" data-target="#modal-gallery"></tbody></table> + </form> + <br> + <div class="well"> + <h3>Demo Notes</h3> + <ul> + <li>The maximum file size for uploads in this demo is <strong>5 MB</strong> (default file size is unlimited).</li> + <li>Only image files (<strong>JPG, GIF, PNG</strong>) are allowed in this demo (by default there is no file type restriction).</li> + <li>Uploaded files will be deleted automatically after <strong>5 minutes</strong> (demo setting).</li> + <li>You can <strong>drag & drop</strong> files from your desktop on this webpage with Google Chrome, Mozilla Firefox and Apple Safari.</li> + <li>Please refer to the <a href="https://github.com/blueimp/jQuery-File-Upload">project website</a> and <a href="https://github.com/blueimp/jQuery-File-Upload/wiki">documentation</a> for more information.</li> + </ul> + </div> +</div> +<!-- The template to display files available for upload --> +<script id="template-upload" type="text/x-tmpl"> +{% for (var i=0, file; file=o.files[i]; i++) { %} + <tr class="template-upload fade"> + <td class="preview"><span class="fade"></span></td> + <td class="name"><span>{%=file.name%}</span></td> + <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td> + {% if (file.error) { %} + <td class="error" colspan="2"><span class="label label-important">{%=locale.fileupload.error%}</span> {%=locale.fileupload.errors[file.error] || file.error%}</td> + {% } else if (o.files.valid && !i) { %} + <td> + <div class="progress progress-success progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><div class="bar" style="width:0%;"></div></div> + </td> + <td class="start">{% if (!o.options.autoUpload) { %} + <button class="btn btn-primary"> + <i class="icon-upload icon-white"></i> + <span>{%=locale.fileupload.start%}</span> + </button> + {% } %}</td> + {% } else { %} + <td colspan="2"></td> + {% } %} + <td class="cancel">{% if (!i) { %} + <button class="btn btn-warning"> + <i class="icon-ban-circle icon-white"></i> + <span>{%=locale.fileupload.cancel%}</span> + </button> + {% } %}</td> + </tr> +{% } %} +</script> +<!-- The template to display files available for download --> +<script id="template-download" type="text/x-tmpl"> +{% for (var i=0, file; file=o.files[i]; i++) { %} + <tr class="template-download fade"> + {% if (file.error) { %} + <td></td> + <td class="name"><span>{%=file.name%}</span></td> + <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td> + <td class="error" colspan="2"><span class="label label-important">{%=locale.fileupload.error%}</span> {%=locale.fileupload.errors[file.error] || file.error%}</td> + {% } else { %} + <td class="preview">{% if (file.thumbnail_url) { %} + <a href="{%=file.url%}" title="{%=file.name%}" rel="gallery" download="{%=file.name%}"><img src="{%=file.thumbnail_url%}"></a> + {% } %}</td> + <td class="name"> + <a href="{%=file.url%}" title="{%=file.name%}" rel="{%=file.thumbnail_url&&'gallery'%}" download="{%=file.name%}">{%=file.name%}</a> + </td> + <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td> + <td colspan="2"></td> + {% } %} + <td class="delete"> + <button class="btn btn-danger" data-type="{%=file.delete_type%}" data-url="{%=file.delete_url%}"> + <i class="icon-trash icon-white"></i> + <span>{%=locale.fileupload.destroy%}</span> + </button> + <input type="checkbox" name="delete" value="1"> + </td> + </tr> +{% } %} +</script> +<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> +<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min.js"></script> +<!-- The Templates plugin is included to render the upload/download listings --> +<script src="http://blueimp.github.com/JavaScript-Templates/tmpl.min.js"></script> +<!-- The Load Image plugin is included for the preview images and image resizing functionality --> +<script src="http://blueimp.github.com/JavaScript-Load-Image/load-image.min.js"></script> +<!-- The Canvas to Blob plugin is included for image resizing functionality --> +<script src="http://blueimp.github.com/JavaScript-Canvas-to-Blob/canvas-to-blob.min.js"></script> +<!-- jQuery Image Gallery --> +<script src="http://blueimp.github.com/jQuery-Image-Gallery/js/jquery.image-gallery.min.js"></script> +<!-- The Iframe Transport is required for browsers without support for XHR file uploads --> +<script src="js/jquery.iframe-transport.js"></script> +<!-- The basic File Upload plugin --> +<script src="js/jquery.fileupload.js"></script> +<!-- The File Upload file processing plugin --> +<script src="js/jquery.fileupload-fp.js"></script> +<!-- The File Upload user interface plugin --> +<script src="js/jquery.fileupload-ui.js"></script> +<!-- The File Upload jQuery UI plugin --> +<script src="js/jquery.fileupload-jui.js"></script> +<!-- The localization script --> +<script src="js/locale.js"></script> +<!-- The main application script --> +<script src="js/main.js"></script> +<!-- The XDomainRequest Transport is included for cross-domain file deletion for IE8+ --> +<!--[if gte IE 8]><script src="js/cors/jquery.xdr-transport.js"></script><![endif]--> +</body> +</html> diff --git a/vendors/jquery-file-upload/js/cors/jquery.postmessage-transport.js b/vendors/jquery-file-upload/js/cors/jquery.postmessage-transport.js new file mode 100644 index 000000000..931b6352b --- /dev/null +++ b/vendors/jquery-file-upload/js/cors/jquery.postmessage-transport.js @@ -0,0 +1,117 @@ +/* + * jQuery postMessage Transport Plugin 1.1 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint unparam: true, nomen: true */ +/*global define, window, document */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define(['jquery'], factory); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + var counter = 0, + names = [ + 'accepts', + 'cache', + 'contents', + 'contentType', + 'crossDomain', + 'data', + 'dataType', + 'headers', + 'ifModified', + 'mimeType', + 'password', + 'processData', + 'timeout', + 'traditional', + 'type', + 'url', + 'username' + ], + convert = function (p) { + return p; + }; + + $.ajaxSetup({ + converters: { + 'postmessage text': convert, + 'postmessage json': convert, + 'postmessage html': convert + } + }); + + $.ajaxTransport('postmessage', function (options) { + if (options.postMessage && window.postMessage) { + var iframe, + loc = $('<a>').prop('href', options.postMessage)[0], + target = loc.protocol + '//' + loc.host, + xhrUpload = options.xhr().upload; + return { + send: function (_, completeCallback) { + var message = { + id: 'postmessage-transport-' + (counter += 1) + }, + eventName = 'message.' + message.id; + iframe = $( + '<iframe style="display:none;" src="' + + options.postMessage + '" name="' + + message.id + '"></iframe>' + ).bind('load', function () { + $.each(names, function (i, name) { + message[name] = options[name]; + }); + message.dataType = message.dataType.replace('postmessage ', ''); + $(window).bind(eventName, function (e) { + e = e.originalEvent; + var data = e.data, + ev; + if (e.origin === target && data.id === message.id) { + if (data.type === 'progress') { + ev = document.createEvent('Event'); + ev.initEvent(data.type, false, true); + $.extend(ev, data); + xhrUpload.dispatchEvent(ev); + } else { + completeCallback( + data.status, + data.statusText, + {postmessage: data.result}, + data.headers + ); + iframe.remove(); + $(window).unbind(eventName); + } + } + }); + iframe[0].contentWindow.postMessage( + message, + target + ); + }).appendTo(document.body); + }, + abort: function () { + if (iframe) { + iframe.remove(); + } + } + }; + } + }); + +})); diff --git a/vendors/jquery-file-upload/js/cors/jquery.xdr-transport.js b/vendors/jquery-file-upload/js/cors/jquery.xdr-transport.js new file mode 100644 index 000000000..c42c54828 --- /dev/null +++ b/vendors/jquery-file-upload/js/cors/jquery.xdr-transport.js @@ -0,0 +1,85 @@ +/* + * jQuery XDomainRequest Transport Plugin 1.1.2 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + * + * Based on Julian Aubourg's ajaxHooks xdr.js: + * https://github.com/jaubourg/ajaxHooks/ + */ + +/*jslint unparam: true */ +/*global define, window, XDomainRequest */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define(['jquery'], factory); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + if (window.XDomainRequest && !$.support.cors) { + $.ajaxTransport(function (s) { + if (s.crossDomain && s.async) { + if (s.timeout) { + s.xdrTimeout = s.timeout; + delete s.timeout; + } + var xdr; + return { + send: function (headers, completeCallback) { + function callback(status, statusText, responses, responseHeaders) { + xdr.onload = xdr.onerror = xdr.ontimeout = $.noop; + xdr = null; + completeCallback(status, statusText, responses, responseHeaders); + } + xdr = new XDomainRequest(); + // XDomainRequest only supports GET and POST: + if (s.type === 'DELETE') { + s.url = s.url + (/\?/.test(s.url) ? '&' : '?') + + '_method=DELETE'; + s.type = 'POST'; + } else if (s.type === 'PUT') { + s.url = s.url + (/\?/.test(s.url) ? '&' : '?') + + '_method=PUT'; + s.type = 'POST'; + } + xdr.open(s.type, s.url); + xdr.onload = function () { + callback( + 200, + 'OK', + {text: xdr.responseText}, + 'Content-Type: ' + xdr.contentType + ); + }; + xdr.onerror = function () { + callback(404, 'Not Found'); + }; + if (s.xdrTimeout) { + xdr.ontimeout = function () { + callback(0, 'timeout'); + }; + xdr.timeout = s.xdrTimeout; + } + xdr.send((s.hasContent && s.data) || null); + }, + abort: function () { + if (xdr) { + xdr.onerror = $.noop(); + xdr.abort(); + } + } + }; + } + }); + } +})); diff --git a/vendors/jquery-file-upload/js/jquery.fileupload-fp.js b/vendors/jquery-file-upload/js/jquery.fileupload-fp.js new file mode 100644 index 000000000..634fb5e4e --- /dev/null +++ b/vendors/jquery-file-upload/js/jquery.fileupload-fp.js @@ -0,0 +1,219 @@ +/* + * jQuery File Upload File Processing Plugin 1.0 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2012, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true, regexp: true */ +/*global define, window, document */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'load-image', + 'canvas-to-blob', + './jquery.fileupload' + ], factory); + } else { + // Browser globals: + factory( + window.jQuery, + window.loadImage + ); + } +}(function ($, loadImage) { + 'use strict'; + + // The File Upload IP version extends the basic fileupload widget + // with file processing functionality: + $.widget('blueimpFP.fileupload', $.blueimp.fileupload, { + + options: { + // The list of file processing actions: + process: [ + /* + { + action: 'load', + fileTypes: /^image\/(gif|jpeg|png)$/, + maxFileSize: 20000000 // 20MB + }, + { + action: 'resize', + maxWidth: 1920, + maxHeight: 1200, + minWidth: 800, + minHeight: 600 + }, + { + action: 'save' + } + */ + ], + + // The add callback is invoked as soon as files are added to the + // fileupload widget (via file input selection, drag & drop or add + // API call). See the basic file upload widget for more information: + add: function (e, data) { + $(this).fileupload('process', data).done(function () { + data.submit(); + }); + } + }, + + processActions: { + // Loads the image given via data.files and data.index + // as canvas element. + // Accepts the options fileTypes (regular expression) + // and maxFileSize (integer) to limit the files to load: + load: function (data, options) { + var that = this, + file = data.files[data.index], + dfd = $.Deferred(); + if (window.HTMLCanvasElement && + window.HTMLCanvasElement.prototype.toBlob && + ($.type(options.maxFileSize) !== 'number' || + file.size < options.maxFileSize) && + (!options.fileTypes || + options.fileTypes.test(file.type))) { + loadImage( + file, + function (canvas) { + data.canvas = canvas; + dfd.resolveWith(that, [data]); + }, + {canvas: true} + ); + } else { + dfd.rejectWith(that, [data]); + } + return dfd.promise(); + }, + // Resizes the image given as data.canvas and updates + // data.canvas with the resized image. + // Accepts the options maxWidth, maxHeight, minWidth and + // minHeight to scale the given image: + resize: function (data, options) { + if (data.canvas) { + var canvas = loadImage.scale(data.canvas, options); + if (canvas.width !== data.canvas.width || + canvas.height !== data.canvas.height) { + data.canvas = canvas; + data.processed = true; + } + } + return data; + }, + // Saves the processed image given as data.canvas + // inplace at data.index of data.files: + save: function (data, options) { + // Do nothing if no processing has happened: + if (!data.canvas || !data.processed) { + return data; + } + var that = this, + file = data.files[data.index], + name = file.name, + dfd = $.Deferred(), + callback = function (blob) { + if (!blob.name) { + if (file.type === blob.type) { + blob.name = file.name; + } else if (file.name) { + blob.name = file.name.replace( + /\..+$/, + '.' + blob.type.substr(6) + ); + } + } + // Store the created blob at the position + // of the original file in the files list: + data.files[data.index] = blob; + dfd.resolveWith(that, [data]); + }; + // Use canvas.mozGetAsFile directly, to retain the filename, as + // Gecko doesn't support the filename option for FormData.append: + if (data.canvas.mozGetAsFile) { + callback(data.canvas.mozGetAsFile( + (/^image\/(jpeg|png)$/.test(file.type) && name) || + ((name && name.replace(/\..+$/, '')) || + 'blob') + '.png', + file.type + )); + } else { + data.canvas.toBlob(callback, file.type); + } + return dfd.promise(); + } + }, + + // Resizes the file at the given index and stores the created blob at + // the original position of the files list, returns a Promise object: + _processFile: function (files, index, options) { + var that = this, + dfd = $.Deferred().resolveWith(that, [{ + files: files, + index: index + }]), + chain = dfd.promise(); + that._processing += 1; + $.each(options.process, function (i, settings) { + chain = chain.pipe(function (data) { + return that.processActions[settings.action] + .call(this, data, settings); + }); + }); + chain.always(function () { + that._processing -= 1; + if (that._processing === 0) { + that.element + .removeClass('fileupload-processing'); + } + }); + if (that._processing === 1) { + that.element.addClass('fileupload-processing'); + } + return chain; + }, + + // Processes the files given as files property of the data parameter, + // returns a Promise object that allows to bind a done handler, which + // will be invoked after processing all files (inplace) is done: + process: function (data) { + var that = this, + options = $.extend({}, this.options, data); + if (options.process && options.process.length && + this._isXHRUpload(options)) { + $.each(data.files, function (index, file) { + that._processingQueue = that._processingQueue.pipe( + function () { + var dfd = $.Deferred(); + that._processFile(data.files, index, options) + .always(function () { + dfd.resolveWith(that); + }); + return dfd.promise(); + } + ); + }); + } + return this._processingQueue; + }, + + _create: function () { + $.blueimp.fileupload.prototype._create.call(this); + this._processing = 0; + this._processingQueue = $.Deferred().resolveWith(this) + .promise(); + } + + }); + +})); diff --git a/vendors/jquery-file-upload/js/jquery.fileupload-jui.js b/vendors/jquery-file-upload/js/jquery.fileupload-jui.js new file mode 100644 index 000000000..69736a4a4 --- /dev/null +++ b/vendors/jquery-file-upload/js/jquery.fileupload-jui.js @@ -0,0 +1,141 @@ +/* + * jQuery File Upload jQuery UI Plugin 1.2 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2012, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true */ +/*global define, window */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define(['jquery', './jquery.fileupload-ui.js'], factory); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + $.widget('blueimpJUI.fileupload', $.blueimpUI.fileupload, { + options: { + sent: function (e, data) { + if (data.context && data.dataType && + data.dataType.substr(0, 6) === 'iframe') { + // Iframe Transport does not support progress events. + // In lack of an indeterminate progress bar, we set + // the progress to 100%, showing the full animated bar: + data.context + .find('.progress').progressbar( + 'option', + 'value', + 100 + ); + } + }, + progress: function (e, data) { + if (data.context) { + data.context.find('.progress').progressbar( + 'option', + 'value', + parseInt(data.loaded / data.total * 100, 10) + ); + } + }, + progressall: function (e, data) { + var $this = $(this); + $this.find('.fileupload-progress') + .find('.progress').progressbar( + 'option', + 'value', + parseInt(data.loaded / data.total * 100, 10) + ).end() + .find('.progress-extended').each(function () { + $(this).html( + $this.data('fileupload') + ._renderExtendedProgress(data) + ); + }); + } + }, + _renderUpload: function (func, files) { + var node = $.blueimpUI.fileupload.prototype + ._renderUpload.call(this, func, files), + showIconText = $(window).width() > 480; + node.find('.progress').empty().progressbar(); + node.find('.start button').button({ + icons: {primary: 'ui-icon-circle-arrow-e'}, + text: showIconText + }); + node.find('.cancel button').button({ + icons: {primary: 'ui-icon-cancel'}, + text: showIconText + }); + return node; + }, + _renderDownload: function (func, files) { + var node = $.blueimpUI.fileupload.prototype + ._renderDownload.call(this, func, files), + showIconText = $(window).width() > 480; + node.find('.delete button').button({ + icons: {primary: 'ui-icon-trash'}, + text: showIconText + }); + return node; + }, + _transition: function (node) { + var that = this, + deferred = $.Deferred(); + if (node.hasClass('fade')) { + node.fadeToggle(function () { + deferred.resolveWith(node); + }); + } else { + deferred.resolveWith(node); + } + return deferred; + }, + _create: function () { + $.blueimpUI.fileupload.prototype._create.call(this); + this.element + .find('.fileupload-buttonbar') + .find('.fileinput-button').each(function () { + var input = $(this).find('input:file').detach(); + $(this) + .button({icons: {primary: 'ui-icon-plusthick'}}) + .append(input); + }) + .end().find('.start') + .button({icons: {primary: 'ui-icon-circle-arrow-e'}}) + .end().find('.cancel') + .button({icons: {primary: 'ui-icon-cancel'}}) + .end().find('.delete') + .button({icons: {primary: 'ui-icon-trash'}}) + .end().find('.progress').empty().progressbar(); + }, + destroy: function () { + this.element + .find('.fileupload-buttonbar') + .find('.fileinput-button').each(function () { + var input = $(this).find('input:file').detach(); + $(this) + .button('destroy') + .append(input); + }) + .end().find('.start') + .button('destroy') + .end().find('.cancel') + .button('destroy') + .end().find('.delete') + .button('destroy') + .end().find('.progress').progressbar('destroy'); + $.blueimpUI.fileupload.prototype.destroy.call(this); + } + }); +})); diff --git a/vendors/jquery-file-upload/js/jquery.fileupload-ui.js b/vendors/jquery-file-upload/js/jquery.fileupload-ui.js new file mode 100644 index 000000000..4c36f0086 --- /dev/null +++ b/vendors/jquery-file-upload/js/jquery.fileupload-ui.js @@ -0,0 +1,736 @@ +/* + * jQuery File Upload User Interface Plugin 6.9.4 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true, regexp: true */ +/*global define, window, document, URL, webkitURL, FileReader */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'tmpl', + 'load-image', + './jquery.fileupload-fp' + ], factory); + } else { + // Browser globals: + factory( + window.jQuery, + window.tmpl, + window.loadImage + ); + } +}(function ($, tmpl, loadImage) { + 'use strict'; + + // The UI version extends the FP (file processing) version or the basic + // file upload widget and adds complete user interface interaction: + var parentWidget = ($.blueimpFP || $.blueimp).fileupload; + $.widget('blueimpUI.fileupload', parentWidget, { + + options: { + // By default, files added to the widget are uploaded as soon + // as the user clicks on the start buttons. To enable automatic + // uploads, set the following option to true: + autoUpload: false, + // The following option limits the number of files that are + // allowed to be uploaded using this widget: + maxNumberOfFiles: undefined, + // The maximum allowed file size: + maxFileSize: undefined, + // The minimum allowed file size: + minFileSize: undefined, + // The regular expression for allowed file types, matches + // against either file type or file name: + acceptFileTypes: /.+$/i, + // The regular expression to define for which files a preview + // image is shown, matched against the file type: + previewSourceFileTypes: /^image\/(gif|jpeg|png)$/, + // The maximum file size of images that are to be displayed as preview: + previewSourceMaxFileSize: 5000000, // 5MB + // The maximum width of the preview images: + previewMaxWidth: 80, + // The maximum height of the preview images: + previewMaxHeight: 80, + // By default, preview images are displayed as canvas elements + // if supported by the browser. Set the following option to false + // to always display preview images as img elements: + previewAsCanvas: true, + // The ID of the upload template: + uploadTemplateId: 'template-upload', + // The ID of the download template: + downloadTemplateId: 'template-download', + // The container for the list of files. If undefined, it is set to + // an element with class "files" inside of the widget element: + filesContainer: undefined, + // By default, files are appended to the files container. + // Set the following option to true, to prepend files instead: + prependFiles: false, + // The expected data type of the upload response, sets the dataType + // option of the $.ajax upload requests: + dataType: 'json', + + // The add callback is invoked as soon as files are added to the fileupload + // widget (via file input selection, drag & drop or add API call). + // See the basic file upload widget for more information: + add: function (e, data) { + var that = $(this).data('fileupload'), + options = that.options, + files = data.files; + $(this).fileupload('process', data).done(function () { + that._adjustMaxNumberOfFiles(-files.length); + data.maxNumberOfFilesAdjusted = true; + data.files.valid = data.isValidated = that._validate(files); + data.context = that._renderUpload(files).data('data', data); + options.filesContainer[ + options.prependFiles ? 'prepend' : 'append' + ](data.context); + that._renderPreviews(files, data.context); + that._forceReflow(data.context); + that._transition(data.context).done( + function () { + if ((that._trigger('added', e, data) !== false) && + (options.autoUpload || data.autoUpload) && + data.autoUpload !== false && data.isValidated) { + data.submit(); + } + } + ); + }); + }, + // Callback for the start of each file upload request: + send: function (e, data) { + var that = $(this).data('fileupload'); + if (!data.isValidated) { + if (!data.maxNumberOfFilesAdjusted) { + that._adjustMaxNumberOfFiles(-data.files.length); + data.maxNumberOfFilesAdjusted = true; + } + if (!that._validate(data.files)) { + return false; + } + } + if (data.context && data.dataType && + data.dataType.substr(0, 6) === 'iframe') { + // Iframe Transport does not support progress events. + // In lack of an indeterminate progress bar, we set + // the progress to 100%, showing the full animated bar: + data.context + .find('.progress').addClass( + !$.support.transition && 'progress-animated' + ) + .attr('aria-valuenow', 100) + .find('.bar').css( + 'width', + '100%' + ); + } + return that._trigger('sent', e, data); + }, + // Callback for successful uploads: + done: function (e, data) { + var that = $(this).data('fileupload'), + template; + if (data.context) { + data.context.each(function (index) { + var file = ($.isArray(data.result) && + data.result[index]) || {error: 'emptyResult'}; + if (file.error) { + that._adjustMaxNumberOfFiles(1); + } + that._transition($(this)).done( + function () { + var node = $(this); + template = that._renderDownload([file]) + .replaceAll(node); + that._forceReflow(template); + that._transition(template).done( + function () { + data.context = $(this); + that._trigger('completed', e, data); + } + ); + } + ); + }); + } else { + if ($.isArray(data.result)) { + $.each(data.result, function (index, file) { + if (data.maxNumberOfFilesAdjusted && file.error) { + that._adjustMaxNumberOfFiles(1); + } else if (!data.maxNumberOfFilesAdjusted && + !file.error) { + that._adjustMaxNumberOfFiles(-1); + } + }); + data.maxNumberOfFilesAdjusted = true; + } + template = that._renderDownload(data.result) + .appendTo(that.options.filesContainer); + that._forceReflow(template); + that._transition(template).done( + function () { + data.context = $(this); + that._trigger('completed', e, data); + } + ); + } + }, + // Callback for failed (abort or error) uploads: + fail: function (e, data) { + var that = $(this).data('fileupload'), + template; + if (data.maxNumberOfFilesAdjusted) { + that._adjustMaxNumberOfFiles(data.files.length); + } + if (data.context) { + data.context.each(function (index) { + if (data.errorThrown !== 'abort') { + var file = data.files[index]; + file.error = file.error || data.errorThrown || + true; + that._transition($(this)).done( + function () { + var node = $(this); + template = that._renderDownload([file]) + .replaceAll(node); + that._forceReflow(template); + that._transition(template).done( + function () { + data.context = $(this); + that._trigger('failed', e, data); + } + ); + } + ); + } else { + that._transition($(this)).done( + function () { + $(this).remove(); + that._trigger('failed', e, data); + } + ); + } + }); + } else if (data.errorThrown !== 'abort') { + data.context = that._renderUpload(data.files) + .appendTo(that.options.filesContainer) + .data('data', data); + that._forceReflow(data.context); + that._transition(data.context).done( + function () { + data.context = $(this); + that._trigger('failed', e, data); + } + ); + } else { + that._trigger('failed', e, data); + } + }, + // Callback for upload progress events: + progress: function (e, data) { + if (data.context) { + var progress = parseInt(data.loaded / data.total * 100, 10); + data.context.find('.progress') + .attr('aria-valuenow', progress) + .find('.bar').css( + 'width', + progress + '%' + ); + } + }, + // Callback for global upload progress events: + progressall: function (e, data) { + var $this = $(this), + progress = parseInt(data.loaded / data.total * 100, 10), + globalProgressNode = $this.find('.fileupload-progress'), + extendedProgressNode = globalProgressNode + .find('.progress-extended'); + if (extendedProgressNode.length) { + extendedProgressNode.html( + $this.data('fileupload')._renderExtendedProgress(data) + ); + } + globalProgressNode + .find('.progress') + .attr('aria-valuenow', progress) + .find('.bar').css( + 'width', + progress + '%' + ); + }, + // Callback for uploads start, equivalent to the global ajaxStart event: + start: function (e) { + var that = $(this).data('fileupload'); + that._transition($(this).find('.fileupload-progress')).done( + function () { + that._trigger('started', e); + } + ); + }, + // Callback for uploads stop, equivalent to the global ajaxStop event: + stop: function (e) { + var that = $(this).data('fileupload'); + that._transition($(this).find('.fileupload-progress')).done( + function () { + $(this).find('.progress') + .attr('aria-valuenow', '0') + .find('.bar').css('width', '0%'); + $(this).find('.progress-extended').html(' '); + that._trigger('stopped', e); + } + ); + }, + // Callback for file deletion: + destroy: function (e, data) { + var that = $(this).data('fileupload'); + if (data.url) { + $.ajax(data); + that._adjustMaxNumberOfFiles(1); + } + that._transition(data.context).done( + function () { + $(this).remove(); + that._trigger('destroyed', e, data); + } + ); + } + }, + + // Link handler, that allows to download files + // by drag & drop of the links to the desktop: + _enableDragToDesktop: function () { + var link = $(this), + url = link.prop('href'), + name = link.prop('download'), + type = 'application/octet-stream'; + link.bind('dragstart', function (e) { + try { + e.originalEvent.dataTransfer.setData( + 'DownloadURL', + [type, name, url].join(':') + ); + } catch (err) {} + }); + }, + + _adjustMaxNumberOfFiles: function (operand) { + if (typeof this.options.maxNumberOfFiles === 'number') { + this.options.maxNumberOfFiles += operand; + if (this.options.maxNumberOfFiles < 1) { + this._disableFileInputButton(); + } else { + this._enableFileInputButton(); + } + } + }, + + _formatFileSize: function (bytes) { + if (typeof bytes !== 'number') { + return ''; + } + if (bytes >= 1000000000) { + return (bytes / 1000000000).toFixed(2) + ' GB'; + } + if (bytes >= 1000000) { + return (bytes / 1000000).toFixed(2) + ' MB'; + } + return (bytes / 1000).toFixed(2) + ' KB'; + }, + + _formatBitrate: function (bits) { + if (typeof bits !== 'number') { + return ''; + } + if (bits >= 1000000000) { + return (bits / 1000000000).toFixed(2) + ' Gbit/s'; + } + if (bits >= 1000000) { + return (bits / 1000000).toFixed(2) + ' Mbit/s'; + } + if (bits >= 1000) { + return (bits / 1000).toFixed(2) + ' kbit/s'; + } + return bits + ' bit/s'; + }, + + _formatTime: function (seconds) { + var date = new Date(seconds * 1000), + days = parseInt(seconds / 86400, 10); + days = days ? days + 'd ' : ''; + return days + + ('0' + date.getUTCHours()).slice(-2) + ':' + + ('0' + date.getUTCMinutes()).slice(-2) + ':' + + ('0' + date.getUTCSeconds()).slice(-2); + }, + + _formatPercentage: function (floatValue) { + return (floatValue * 100).toFixed(2) + ' %'; + }, + + _renderExtendedProgress: function (data) { + return this._formatBitrate(data.bitrate) + ' | ' + + this._formatTime( + (data.total - data.loaded) * 8 / data.bitrate + ) + ' | ' + + this._formatPercentage( + data.loaded / data.total + ) + ' | ' + + this._formatFileSize(data.loaded) + ' / ' + + this._formatFileSize(data.total); + }, + + _hasError: function (file) { + if (file.error) { + return file.error; + } + // The number of added files is subtracted from + // maxNumberOfFiles before validation, so we check if + // maxNumberOfFiles is below 0 (instead of below 1): + if (this.options.maxNumberOfFiles < 0) { + return 'maxNumberOfFiles'; + } + // Files are accepted if either the file type or the file name + // matches against the acceptFileTypes regular expression, as + // only browsers with support for the File API report the type: + if (!(this.options.acceptFileTypes.test(file.type) || + this.options.acceptFileTypes.test(file.name))) { + return 'acceptFileTypes'; + } + if (this.options.maxFileSize && + file.size > this.options.maxFileSize) { + return 'maxFileSize'; + } + if (typeof file.size === 'number' && + file.size < this.options.minFileSize) { + return 'minFileSize'; + } + return null; + }, + + _validate: function (files) { + var that = this, + valid = !!files.length; + $.each(files, function (index, file) { + file.error = that._hasError(file); + if (file.error) { + valid = false; + } + }); + return valid; + }, + + _renderTemplate: function (func, files) { + if (!func) { + return $(); + } + var result = func({ + files: files, + formatFileSize: this._formatFileSize, + options: this.options + }); + if (result instanceof $) { + return result; + } + return $(this.options.templatesContainer).html(result).children(); + }, + + _renderPreview: function (file, node) { + var that = this, + options = this.options, + dfd = $.Deferred(); + return ((loadImage && loadImage( + file, + function (img) { + node.append(img); + that._forceReflow(node); + that._transition(node).done(function () { + dfd.resolveWith(node); + }); + if (!$.contains(document.body, node[0])) { + // If the element is not part of the DOM, + // transition events are not triggered, + // so we have to resolve manually: + dfd.resolveWith(node); + } + }, + { + maxWidth: options.previewMaxWidth, + maxHeight: options.previewMaxHeight, + canvas: options.previewAsCanvas + } + )) || dfd.resolveWith(node)) && dfd; + }, + + _renderPreviews: function (files, nodes) { + var that = this, + options = this.options; + nodes.find('.preview span').each(function (index, element) { + var file = files[index]; + if (options.previewSourceFileTypes.test(file.type) && + ($.type(options.previewSourceMaxFileSize) !== 'number' || + file.size < options.previewSourceMaxFileSize)) { + that._processingQueue = that._processingQueue.pipe(function () { + var dfd = $.Deferred(); + that._renderPreview(file, $(element)).done( + function () { + dfd.resolveWith(that); + } + ); + return dfd.promise(); + }); + } + }); + return this._processingQueue; + }, + + _renderUpload: function (files) { + return this._renderTemplate( + this.options.uploadTemplate, + files + ); + }, + + _renderDownload: function (files) { + return this._renderTemplate( + this.options.downloadTemplate, + files + ).find('a[download]').each(this._enableDragToDesktop).end(); + }, + + _startHandler: function (e) { + e.preventDefault(); + var button = $(this), + template = button.closest('.template-upload'), + data = template.data('data'); + if (data && data.submit && !data.jqXHR && data.submit()) { + button.prop('disabled', true); + } + }, + + _cancelHandler: function (e) { + e.preventDefault(); + var template = $(this).closest('.template-upload'), + data = template.data('data') || {}; + if (!data.jqXHR) { + data.errorThrown = 'abort'; + e.data.fileupload._trigger('fail', e, data); + } else { + data.jqXHR.abort(); + } + }, + + _deleteHandler: function (e) { + e.preventDefault(); + var button = $(this); + e.data.fileupload._trigger('destroy', e, { + context: button.closest('.template-download'), + url: button.attr('data-url'), + type: button.attr('data-type') || 'DELETE', + dataType: e.data.fileupload.options.dataType + }); + }, + + _forceReflow: function (node) { + return $.support.transition && node.length && + node[0].offsetWidth; + }, + + _transition: function (node) { + var dfd = $.Deferred(); + if ($.support.transition && node.hasClass('fade')) { + node.bind( + $.support.transition.end, + function (e) { + // Make sure we don't respond to other transitions events + // in the container element, e.g. from button elements: + if (e.target === node[0]) { + node.unbind($.support.transition.end); + dfd.resolveWith(node); + } + } + ).toggleClass('in'); + } else { + node.toggleClass('in'); + dfd.resolveWith(node); + } + return dfd; + }, + + _initButtonBarEventHandlers: function () { + var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'), + filesList = this.options.filesContainer, + ns = this.options.namespace; + fileUploadButtonBar.find('.start') + .bind('click.' + ns, function (e) { + e.preventDefault(); + filesList.find('.start button').click(); + }); + fileUploadButtonBar.find('.cancel') + .bind('click.' + ns, function (e) { + e.preventDefault(); + filesList.find('.cancel button').click(); + }); + fileUploadButtonBar.find('.delete') + .bind('click.' + ns, function (e) { + e.preventDefault(); + filesList.find('.delete input:checked') + .siblings('button').click(); + fileUploadButtonBar.find('.toggle') + .prop('checked', false); + }); + fileUploadButtonBar.find('.toggle') + .bind('change.' + ns, function (e) { + filesList.find('.delete input').prop( + 'checked', + $(this).is(':checked') + ); + }); + }, + + _destroyButtonBarEventHandlers: function () { + this.element.find('.fileupload-buttonbar button') + .unbind('click.' + this.options.namespace); + this.element.find('.fileupload-buttonbar .toggle') + .unbind('change.' + this.options.namespace); + }, + + _initEventHandlers: function () { + parentWidget.prototype._initEventHandlers.call(this); + var eventData = {fileupload: this}; + this.options.filesContainer + .delegate( + '.start button', + 'click.' + this.options.namespace, + eventData, + this._startHandler + ) + .delegate( + '.cancel button', + 'click.' + this.options.namespace, + eventData, + this._cancelHandler + ) + .delegate( + '.delete button', + 'click.' + this.options.namespace, + eventData, + this._deleteHandler + ); + this._initButtonBarEventHandlers(); + }, + + _destroyEventHandlers: function () { + var options = this.options; + this._destroyButtonBarEventHandlers(); + options.filesContainer + .undelegate('.start button', 'click.' + options.namespace) + .undelegate('.cancel button', 'click.' + options.namespace) + .undelegate('.delete button', 'click.' + options.namespace); + parentWidget.prototype._destroyEventHandlers.call(this); + }, + + _enableFileInputButton: function () { + this.element.find('.fileinput-button input') + .prop('disabled', false) + .parent().removeClass('disabled'); + }, + + _disableFileInputButton: function () { + this.element.find('.fileinput-button input') + .prop('disabled', true) + .parent().addClass('disabled'); + }, + + _initTemplates: function () { + var options = this.options; + options.templatesContainer = document.createElement( + options.filesContainer.prop('nodeName') + ); + if (tmpl) { + if (options.uploadTemplateId) { + options.uploadTemplate = tmpl(options.uploadTemplateId); + } + if (options.downloadTemplateId) { + options.downloadTemplate = tmpl(options.downloadTemplateId); + } + } + }, + + _initFilesContainer: function () { + var options = this.options; + if (options.filesContainer === undefined) { + options.filesContainer = this.element.find('.files'); + } else if (!(options.filesContainer instanceof $)) { + options.filesContainer = $(options.filesContainer); + } + }, + + _stringToRegExp: function (str) { + var parts = str.split('/'), + modifiers = parts.pop(); + parts.shift(); + return new RegExp(parts.join('/'), modifiers); + }, + + _initRegExpOptions: function () { + var options = this.options; + if ($.type(options.acceptFileTypes) === 'string') { + options.acceptFileTypes = this._stringToRegExp( + options.acceptFileTypes + ); + } + if ($.type(options.previewSourceFileTypes) === 'string') { + options.previewSourceFileTypes = this._stringToRegExp( + options.previewSourceFileTypes + ); + } + }, + + _initSpecialOptions: function () { + parentWidget.prototype._initSpecialOptions.call(this); + this._initFilesContainer(); + this._initTemplates(); + this._initRegExpOptions(); + }, + + _create: function () { + parentWidget.prototype._create.call(this); + this._refreshOptionsList.push( + 'filesContainer', + 'uploadTemplateId', + 'downloadTemplateId' + ); + if (!$.blueimpFP) { + this._processingQueue = $.Deferred().resolveWith(this).promise(); + this.process = function () { + return this._processingQueue; + }; + } + }, + + enable: function () { + parentWidget.prototype.enable.call(this); + this.element.find('input, button').prop('disabled', false); + this._enableFileInputButton(); + }, + + disable: function () { + this.element.find('input, button').prop('disabled', true); + this._disableFileInputButton(); + parentWidget.prototype.disable.call(this); + } + + }); + +})); diff --git a/vendors/jquery-file-upload/js/jquery.fileupload.js b/vendors/jquery-file-upload/js/jquery.fileupload.js new file mode 100644 index 000000000..4bbd28729 --- /dev/null +++ b/vendors/jquery-file-upload/js/jquery.fileupload.js @@ -0,0 +1,972 @@ +/* + * jQuery File Upload Plugin 5.13 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true, regexp: true */ +/*global define, window, document, Blob, FormData, location */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'jquery.ui.widget' + ], factory); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + // The FileReader API is not actually used, but works as feature detection, + // as e.g. Safari supports XHR file uploads via the FormData API, + // but not non-multipart XHR file uploads: + $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader); + $.support.xhrFormDataFileUpload = !!window.FormData; + + // The fileupload widget listens for change events on file input fields defined + // via fileInput setting and paste or drop events of the given dropZone. + // In addition to the default jQuery Widget methods, the fileupload widget + // exposes the "add" and "send" methods, to add or directly send files using + // the fileupload API. + // By default, files added via file input selection, paste, drag & drop or + // "add" method are uploaded immediately, but it is possible to override + // the "add" callback option to queue file uploads. + $.widget('blueimp.fileupload', { + + options: { + // The namespace used for event handler binding on the dropZone and + // fileInput collections. + // If not set, the name of the widget ("fileupload") is used. + namespace: undefined, + // The drop target collection, by the default the complete document. + // Set to null or an empty collection to disable drag & drop support: + dropZone: $(document), + // The file input field collection, that is listened for change events. + // If undefined, it is set to the file input fields inside + // of the widget element on plugin initialization. + // Set to null or an empty collection to disable the change listener. + fileInput: undefined, + // By default, the file input field is replaced with a clone after + // each input field change event. This is required for iframe transport + // queues and allows change events to be fired for the same file + // selection, but can be disabled by setting the following option to false: + replaceFileInput: true, + // The parameter name for the file form data (the request argument name). + // If undefined or empty, the name property of the file input field is + // used, or "files[]" if the file input name property is also empty, + // can be a string or an array of strings: + paramName: undefined, + // By default, each file of a selection is uploaded using an individual + // request for XHR type uploads. Set to false to upload file + // selections in one request each: + singleFileUploads: true, + // To limit the number of files uploaded with one XHR request, + // set the following option to an integer greater than 0: + limitMultiFileUploads: undefined, + // Set the following option to true to issue all file upload requests + // in a sequential order: + sequentialUploads: false, + // To limit the number of concurrent uploads, + // set the following option to an integer greater than 0: + limitConcurrentUploads: undefined, + // Set the following option to true to force iframe transport uploads: + forceIframeTransport: false, + // Set the following option to the location of a redirect url on the + // origin server, for cross-domain iframe transport uploads: + redirect: undefined, + // The parameter name for the redirect url, sent as part of the form + // data and set to 'redirect' if this option is empty: + redirectParamName: undefined, + // Set the following option to the location of a postMessage window, + // to enable postMessage transport uploads: + postMessage: undefined, + // By default, XHR file uploads are sent as multipart/form-data. + // The iframe transport is always using multipart/form-data. + // Set to false to enable non-multipart XHR uploads: + multipart: true, + // To upload large files in smaller chunks, set the following option + // to a preferred maximum chunk size. If set to 0, null or undefined, + // or the browser does not support the required Blob API, files will + // be uploaded as a whole. + maxChunkSize: undefined, + // When a non-multipart upload or a chunked multipart upload has been + // aborted, this option can be used to resume the upload by setting + // it to the size of the already uploaded bytes. This option is most + // useful when modifying the options object inside of the "add" or + // "send" callbacks, as the options are cloned for each file upload. + uploadedBytes: undefined, + // By default, failed (abort or error) file uploads are removed from the + // global progress calculation. Set the following option to false to + // prevent recalculating the global progress data: + recalculateProgress: true, + // Interval in milliseconds to calculate and trigger progress events: + progressInterval: 100, + // Interval in milliseconds to calculate progress bitrate: + bitrateInterval: 500, + + // Additional form data to be sent along with the file uploads can be set + // using this option, which accepts an array of objects with name and + // value properties, a function returning such an array, a FormData + // object (for XHR file uploads), or a simple object. + // The form of the first fileInput is given as parameter to the function: + formData: function (form) { + return form.serializeArray(); + }, + + // The add callback is invoked as soon as files are added to the fileupload + // widget (via file input selection, drag & drop, paste or add API call). + // If the singleFileUploads option is enabled, this callback will be + // called once for each file in the selection for XHR file uplaods, else + // once for each file selection. + // The upload starts when the submit method is invoked on the data parameter. + // The data object contains a files property holding the added files + // and allows to override plugin options as well as define ajax settings. + // Listeners for this callback can also be bound the following way: + // .bind('fileuploadadd', func); + // data.submit() returns a Promise object and allows to attach additional + // handlers using jQuery's Deferred callbacks: + // data.submit().done(func).fail(func).always(func); + add: function (e, data) { + data.submit(); + }, + + // Other callbacks: + // Callback for the submit event of each file upload: + // submit: function (e, data) {}, // .bind('fileuploadsubmit', func); + // Callback for the start of each file upload request: + // send: function (e, data) {}, // .bind('fileuploadsend', func); + // Callback for successful uploads: + // done: function (e, data) {}, // .bind('fileuploaddone', func); + // Callback for failed (abort or error) uploads: + // fail: function (e, data) {}, // .bind('fileuploadfail', func); + // Callback for completed (success, abort or error) requests: + // always: function (e, data) {}, // .bind('fileuploadalways', func); + // Callback for upload progress events: + // progress: function (e, data) {}, // .bind('fileuploadprogress', func); + // Callback for global upload progress events: + // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func); + // Callback for uploads start, equivalent to the global ajaxStart event: + // start: function (e) {}, // .bind('fileuploadstart', func); + // Callback for uploads stop, equivalent to the global ajaxStop event: + // stop: function (e) {}, // .bind('fileuploadstop', func); + // Callback for change events of the fileInput collection: + // change: function (e, data) {}, // .bind('fileuploadchange', func); + // Callback for paste events to the dropZone collection: + // paste: function (e, data) {}, // .bind('fileuploadpaste', func); + // Callback for drop events of the dropZone collection: + // drop: function (e, data) {}, // .bind('fileuploaddrop', func); + // Callback for dragover events of the dropZone collection: + // dragover: function (e) {}, // .bind('fileuploaddragover', func); + + // The plugin options are used as settings object for the ajax calls. + // The following are jQuery ajax settings required for the file uploads: + processData: false, + contentType: false, + cache: false + }, + + // A list of options that require a refresh after assigning a new value: + _refreshOptionsList: [ + 'namespace', + 'dropZone', + 'fileInput', + 'multipart', + 'forceIframeTransport' + ], + + _BitrateTimer: function () { + this.timestamp = +(new Date()); + this.loaded = 0; + this.bitrate = 0; + this.getBitrate = function (now, loaded, interval) { + var timeDiff = now - this.timestamp; + if (!this.bitrate || !interval || timeDiff > interval) { + this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8; + this.loaded = loaded; + this.timestamp = now; + } + return this.bitrate; + }; + }, + + _isXHRUpload: function (options) { + return !options.forceIframeTransport && + ((!options.multipart && $.support.xhrFileUpload) || + $.support.xhrFormDataFileUpload); + }, + + _getFormData: function (options) { + var formData; + if (typeof options.formData === 'function') { + return options.formData(options.form); + } + if ($.isArray(options.formData)) { + return options.formData; + } + if (options.formData) { + formData = []; + $.each(options.formData, function (name, value) { + formData.push({name: name, value: value}); + }); + return formData; + } + return []; + }, + + _getTotal: function (files) { + var total = 0; + $.each(files, function (index, file) { + total += file.size || 1; + }); + return total; + }, + + _onProgress: function (e, data) { + if (e.lengthComputable) { + var now = +(new Date()), + total, + loaded; + if (data._time && data.progressInterval && + (now - data._time < data.progressInterval) && + e.loaded !== e.total) { + return; + } + data._time = now; + total = data.total || this._getTotal(data.files); + loaded = parseInt( + e.loaded / e.total * (data.chunkSize || total), + 10 + ) + (data.uploadedBytes || 0); + this._loaded += loaded - (data.loaded || data.uploadedBytes || 0); + data.lengthComputable = true; + data.loaded = loaded; + data.total = total; + data.bitrate = data._bitrateTimer.getBitrate( + now, + loaded, + data.bitrateInterval + ); + // Trigger a custom progress event with a total data property set + // to the file size(s) of the current upload and a loaded data + // property calculated accordingly: + this._trigger('progress', e, data); + // Trigger a global progress event for all current file uploads, + // including ajax calls queued for sequential file uploads: + this._trigger('progressall', e, { + lengthComputable: true, + loaded: this._loaded, + total: this._total, + bitrate: this._bitrateTimer.getBitrate( + now, + this._loaded, + data.bitrateInterval + ) + }); + } + }, + + _initProgressListener: function (options) { + var that = this, + xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr(); + // Accesss to the native XHR object is required to add event listeners + // for the upload progress event: + if (xhr.upload) { + $(xhr.upload).bind('progress', function (e) { + var oe = e.originalEvent; + // Make sure the progress event properties get copied over: + e.lengthComputable = oe.lengthComputable; + e.loaded = oe.loaded; + e.total = oe.total; + that._onProgress(e, options); + }); + options.xhr = function () { + return xhr; + }; + } + }, + + _initXHRData: function (options) { + var formData, + file = options.files[0], + // Ignore non-multipart setting if not supported: + multipart = options.multipart || !$.support.xhrFileUpload, + paramName = options.paramName[0]; + if (!multipart || options.blob) { + // For non-multipart uploads and chunked uploads, + // file meta data is not part of the request body, + // so we transmit this data as part of the HTTP headers. + // For cross domain requests, these headers must be allowed + // via Access-Control-Allow-Headers or removed using + // the beforeSend callback: + options.headers = $.extend(options.headers, { + 'X-File-Name': file.name, + 'X-File-Type': file.type, + 'X-File-Size': file.size + }); + if (!options.blob) { + // Non-chunked non-multipart upload: + options.contentType = file.type; + options.data = file; + } else if (!multipart) { + // Chunked non-multipart upload: + options.contentType = 'application/octet-stream'; + options.data = options.blob; + } + } + if (multipart && $.support.xhrFormDataFileUpload) { + if (options.postMessage) { + // window.postMessage does not allow sending FormData + // objects, so we just add the File/Blob objects to + // the formData array and let the postMessage window + // create the FormData object out of this array: + formData = this._getFormData(options); + if (options.blob) { + formData.push({ + name: paramName, + value: options.blob + }); + } else { + $.each(options.files, function (index, file) { + formData.push({ + name: options.paramName[index] || paramName, + value: file + }); + }); + } + } else { + if (options.formData instanceof FormData) { + formData = options.formData; + } else { + formData = new FormData(); + $.each(this._getFormData(options), function (index, field) { + formData.append(field.name, field.value); + }); + } + if (options.blob) { + formData.append(paramName, options.blob, file.name); + } else { + $.each(options.files, function (index, file) { + // File objects are also Blob instances. + // This check allows the tests to run with + // dummy objects: + if (file instanceof Blob) { + formData.append( + options.paramName[index] || paramName, + file, + file.name + ); + } + }); + } + } + options.data = formData; + } + // Blob reference is not needed anymore, free memory: + options.blob = null; + }, + + _initIframeSettings: function (options) { + // Setting the dataType to iframe enables the iframe transport: + options.dataType = 'iframe ' + (options.dataType || ''); + // The iframe transport accepts a serialized array as form data: + options.formData = this._getFormData(options); + // Add redirect url to form data on cross-domain uploads: + if (options.redirect && $('<a></a>').prop('href', options.url) + .prop('host') !== location.host) { + options.formData.push({ + name: options.redirectParamName || 'redirect', + value: options.redirect + }); + } + }, + + _initDataSettings: function (options) { + if (this._isXHRUpload(options)) { + if (!this._chunkedUpload(options, true)) { + if (!options.data) { + this._initXHRData(options); + } + this._initProgressListener(options); + } + if (options.postMessage) { + // Setting the dataType to postmessage enables the + // postMessage transport: + options.dataType = 'postmessage ' + (options.dataType || ''); + } + } else { + this._initIframeSettings(options, 'iframe'); + } + }, + + _getParamName: function (options) { + var fileInput = $(options.fileInput), + paramName = options.paramName; + if (!paramName) { + paramName = []; + fileInput.each(function () { + var input = $(this), + name = input.prop('name') || 'files[]', + i = (input.prop('files') || [1]).length; + while (i) { + paramName.push(name); + i -= 1; + } + }); + if (!paramName.length) { + paramName = [fileInput.prop('name') || 'files[]']; + } + } else if (!$.isArray(paramName)) { + paramName = [paramName]; + } + return paramName; + }, + + _initFormSettings: function (options) { + // Retrieve missing options from the input field and the + // associated form, if available: + if (!options.form || !options.form.length) { + options.form = $(options.fileInput.prop('form')); + } + options.paramName = this._getParamName(options); + if (!options.url) { + options.url = options.form.prop('action') || location.href; + } + // The HTTP request method must be "POST" or "PUT": + options.type = (options.type || options.form.prop('method') || '') + .toUpperCase(); + if (options.type !== 'POST' && options.type !== 'PUT') { + options.type = 'POST'; + } + }, + + _getAJAXSettings: function (data) { + var options = $.extend({}, this.options, data); + this._initFormSettings(options); + this._initDataSettings(options); + return options; + }, + + // Maps jqXHR callbacks to the equivalent + // methods of the given Promise object: + _enhancePromise: function (promise) { + promise.success = promise.done; + promise.error = promise.fail; + promise.complete = promise.always; + return promise; + }, + + // Creates and returns a Promise object enhanced with + // the jqXHR methods abort, success, error and complete: + _getXHRPromise: function (resolveOrReject, context, args) { + var dfd = $.Deferred(), + promise = dfd.promise(); + context = context || this.options.context || promise; + if (resolveOrReject === true) { + dfd.resolveWith(context, args); + } else if (resolveOrReject === false) { + dfd.rejectWith(context, args); + } + promise.abort = dfd.promise; + return this._enhancePromise(promise); + }, + + // Uploads a file in multiple, sequential requests + // by splitting the file up in multiple blob chunks. + // If the second parameter is true, only tests if the file + // should be uploaded in chunks, but does not invoke any + // upload requests: + _chunkedUpload: function (options, testOnly) { + var that = this, + file = options.files[0], + fs = file.size, + ub = options.uploadedBytes = options.uploadedBytes || 0, + mcs = options.maxChunkSize || fs, + // Use the Blob methods with the slice implementation + // according to the W3C Blob API specification: + slice = file.webkitSlice || file.mozSlice || file.slice, + upload, + n, + jqXHR, + pipe; + if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) || + options.data) { + return false; + } + if (testOnly) { + return true; + } + if (ub >= fs) { + file.error = 'uploadedBytes'; + return this._getXHRPromise( + false, + options.context, + [null, 'error', file.error] + ); + } + // n is the number of blobs to upload, + // calculated via filesize, uploaded bytes and max chunk size: + n = Math.ceil((fs - ub) / mcs); + // The chunk upload method accepting the chunk number as parameter: + upload = function (i) { + if (!i) { + return that._getXHRPromise(true, options.context); + } + // Upload the blobs in sequential order: + return upload(i -= 1).pipe(function () { + // Clone the options object for each chunk upload: + var o = $.extend({}, options); + o.blob = slice.call( + file, + ub + i * mcs, + ub + (i + 1) * mcs + ); + // Expose the chunk index: + o.chunkIndex = i; + // Expose the number of chunks: + o.chunksNumber = n; + // Store the current chunk size, as the blob itself + // will be dereferenced after data processing: + o.chunkSize = o.blob.size; + // Process the upload data (the blob and potential form data): + that._initXHRData(o); + // Add progress listeners for this chunk upload: + that._initProgressListener(o); + jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context)) + .done(function () { + // Create a progress event if upload is done and + // no progress event has been invoked for this chunk: + if (!o.loaded) { + that._onProgress($.Event('progress', { + lengthComputable: true, + loaded: o.chunkSize, + total: o.chunkSize + }), o); + } + options.uploadedBytes = o.uploadedBytes += + o.chunkSize; + }); + return jqXHR; + }); + }; + // Return the piped Promise object, enhanced with an abort method, + // which is delegated to the jqXHR object of the current upload, + // and jqXHR callbacks mapped to the equivalent Promise methods: + pipe = upload(n); + pipe.abort = function () { + return jqXHR.abort(); + }; + return this._enhancePromise(pipe); + }, + + _beforeSend: function (e, data) { + if (this._active === 0) { + // the start callback is triggered when an upload starts + // and no other uploads are currently running, + // equivalent to the global ajaxStart event: + this._trigger('start'); + // Set timer for global bitrate progress calculation: + this._bitrateTimer = new this._BitrateTimer(); + } + this._active += 1; + // Initialize the global progress values: + this._loaded += data.uploadedBytes || 0; + this._total += this._getTotal(data.files); + }, + + _onDone: function (result, textStatus, jqXHR, options) { + if (!this._isXHRUpload(options)) { + // Create a progress event for each iframe load: + this._onProgress($.Event('progress', { + lengthComputable: true, + loaded: 1, + total: 1 + }), options); + } + options.result = result; + options.textStatus = textStatus; + options.jqXHR = jqXHR; + this._trigger('done', null, options); + }, + + _onFail: function (jqXHR, textStatus, errorThrown, options) { + options.jqXHR = jqXHR; + options.textStatus = textStatus; + options.errorThrown = errorThrown; + this._trigger('fail', null, options); + if (options.recalculateProgress) { + // Remove the failed (error or abort) file upload from + // the global progress calculation: + this._loaded -= options.loaded || options.uploadedBytes || 0; + this._total -= options.total || this._getTotal(options.files); + } + }, + + _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) { + this._active -= 1; + options.textStatus = textStatus; + if (jqXHRorError && jqXHRorError.always) { + options.jqXHR = jqXHRorError; + options.result = jqXHRorResult; + } else { + options.jqXHR = jqXHRorResult; + options.errorThrown = jqXHRorError; + } + this._trigger('always', null, options); + if (this._active === 0) { + // The stop callback is triggered when all uploads have + // been completed, equivalent to the global ajaxStop event: + this._trigger('stop'); + // Reset the global progress values: + this._loaded = this._total = 0; + this._bitrateTimer = null; + } + }, + + _onSend: function (e, data) { + var that = this, + jqXHR, + slot, + pipe, + options = that._getAJAXSettings(data), + send = function (resolve, args) { + that._sending += 1; + // Set timer for bitrate progress calculation: + options._bitrateTimer = new that._BitrateTimer(); + jqXHR = jqXHR || ( + (resolve !== false && + that._trigger('send', e, options) !== false && + (that._chunkedUpload(options) || $.ajax(options))) || + that._getXHRPromise(false, options.context, args) + ).done(function (result, textStatus, jqXHR) { + that._onDone(result, textStatus, jqXHR, options); + }).fail(function (jqXHR, textStatus, errorThrown) { + that._onFail(jqXHR, textStatus, errorThrown, options); + }).always(function (jqXHRorResult, textStatus, jqXHRorError) { + that._sending -= 1; + that._onAlways( + jqXHRorResult, + textStatus, + jqXHRorError, + options + ); + if (options.limitConcurrentUploads && + options.limitConcurrentUploads > that._sending) { + // Start the next queued upload, + // that has not been aborted: + var nextSlot = that._slots.shift(); + while (nextSlot) { + if (!nextSlot.isRejected()) { + nextSlot.resolve(); + break; + } + nextSlot = that._slots.shift(); + } + } + }); + return jqXHR; + }; + this._beforeSend(e, options); + if (this.options.sequentialUploads || + (this.options.limitConcurrentUploads && + this.options.limitConcurrentUploads <= this._sending)) { + if (this.options.limitConcurrentUploads > 1) { + slot = $.Deferred(); + this._slots.push(slot); + pipe = slot.pipe(send); + } else { + pipe = (this._sequence = this._sequence.pipe(send, send)); + } + // Return the piped Promise object, enhanced with an abort method, + // which is delegated to the jqXHR object of the current upload, + // and jqXHR callbacks mapped to the equivalent Promise methods: + pipe.abort = function () { + var args = [undefined, 'abort', 'abort']; + if (!jqXHR) { + if (slot) { + slot.rejectWith(args); + } + return send(false, args); + } + return jqXHR.abort(); + }; + return this._enhancePromise(pipe); + } + return send(); + }, + + _onAdd: function (e, data) { + var that = this, + result = true, + options = $.extend({}, this.options, data), + limit = options.limitMultiFileUploads, + paramName = this._getParamName(options), + paramNameSet, + paramNameSlice, + fileSet, + i; + if (!(options.singleFileUploads || limit) || + !this._isXHRUpload(options)) { + fileSet = [data.files]; + paramNameSet = [paramName]; + } else if (!options.singleFileUploads && limit) { + fileSet = []; + paramNameSet = []; + for (i = 0; i < data.files.length; i += limit) { + fileSet.push(data.files.slice(i, i + limit)); + paramNameSlice = paramName.slice(i, i + limit); + if (!paramNameSlice.length) { + paramNameSlice = paramName; + } + paramNameSet.push(paramNameSlice); + } + } else { + paramNameSet = paramName; + } + data.originalFiles = data.files; + $.each(fileSet || data.files, function (index, element) { + var newData = $.extend({}, data); + newData.files = fileSet ? element : [element]; + newData.paramName = paramNameSet[index]; + newData.submit = function () { + newData.jqXHR = this.jqXHR = + (that._trigger('submit', e, this) !== false) && + that._onSend(e, this); + return this.jqXHR; + }; + return (result = that._trigger('add', e, newData)); + }); + return result; + }, + + // File Normalization for Gecko 1.9.1 (Firefox 3.5) support: + _normalizeFile: function (index, file) { + if (file.name === undefined && file.size === undefined) { + file.name = file.fileName; + file.size = file.fileSize; + } + }, + + _replaceFileInput: function (input) { + var inputClone = input.clone(true); + $('<form></form>').append(inputClone)[0].reset(); + // Detaching allows to insert the fileInput on another form + // without loosing the file input value: + input.after(inputClone).detach(); + // Avoid memory leaks with the detached file input: + $.cleanData(input.unbind('remove')); + // Replace the original file input element in the fileInput + // collection with the clone, which has been copied including + // event handlers: + this.options.fileInput = this.options.fileInput.map(function (i, el) { + if (el === input[0]) { + return inputClone[0]; + } + return el; + }); + // If the widget has been initialized on the file input itself, + // override this.element with the file input clone: + if (input[0] === this.element[0]) { + this.element = inputClone; + } + }, + + _getFileInputFiles: function (fileInput) { + fileInput = $(fileInput); + var files = $.each($.makeArray(fileInput.prop('files')), this._normalizeFile), + value; + if (!files.length) { + value = fileInput.prop('value'); + if (!value) { + return []; + } + // If the files property is not available, the browser does not + // support the File API and we add a pseudo File object with + // the input value as name with path information removed: + files = [{name: value.replace(/^.*\\/, '')}]; + } + return files; + }, + + _onChange: function (e) { + var that = e.data.fileupload, + data = { + fileInput: $(e.target), + form: $(e.target.form) + }; + data.files = that._getFileInputFiles(data.fileInput); + if (that.options.replaceFileInput) { + that._replaceFileInput(data.fileInput); + } + if (that._trigger('change', e, data) === false || + that._onAdd(e, data) === false) { + return false; + } + }, + + _onPaste: function (e) { + var that = e.data.fileupload, + cbd = e.originalEvent.clipboardData, + items = (cbd && cbd.items) || [], + data = {files: []}; + $.each(items, function (index, item) { + var file = item.getAsFile && item.getAsFile(); + if (file) { + data.files.push(file); + } + }); + if (that._trigger('paste', e, data) === false || + that._onAdd(e, data) === false) { + return false; + } + }, + + _onDrop: function (e) { + var that = e.data.fileupload, + dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer, + data = { + files: $.each( + $.makeArray(dataTransfer && dataTransfer.files), + that._normalizeFile + ) + }; + if (that._trigger('drop', e, data) === false || + that._onAdd(e, data) === false) { + return false; + } + e.preventDefault(); + }, + + _onDragOver: function (e) { + var that = e.data.fileupload, + dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer; + if (that._trigger('dragover', e) === false) { + return false; + } + if (dataTransfer) { + dataTransfer.dropEffect = 'copy'; + } + e.preventDefault(); + }, + + _initEventHandlers: function () { + var ns = this.options.namespace; + if (this._isXHRUpload(this.options)) { + this.options.dropZone + .bind('dragover.' + ns, {fileupload: this}, this._onDragOver) + .bind('drop.' + ns, {fileupload: this}, this._onDrop) + .bind('paste.' + ns, {fileupload: this}, this._onPaste); + } + this.options.fileInput + .bind('change.' + ns, {fileupload: this}, this._onChange); + }, + + _destroyEventHandlers: function () { + var ns = this.options.namespace; + this.options.dropZone + .unbind('dragover.' + ns, this._onDragOver) + .unbind('drop.' + ns, this._onDrop) + .unbind('paste.' + ns, this._onPaste); + this.options.fileInput + .unbind('change.' + ns, this._onChange); + }, + + _setOption: function (key, value) { + var refresh = $.inArray(key, this._refreshOptionsList) !== -1; + if (refresh) { + this._destroyEventHandlers(); + } + $.Widget.prototype._setOption.call(this, key, value); + if (refresh) { + this._initSpecialOptions(); + this._initEventHandlers(); + } + }, + + _initSpecialOptions: function () { + var options = this.options; + if (options.fileInput === undefined) { + options.fileInput = this.element.is('input:file') ? + this.element : this.element.find('input:file'); + } else if (!(options.fileInput instanceof $)) { + options.fileInput = $(options.fileInput); + } + if (!(options.dropZone instanceof $)) { + options.dropZone = $(options.dropZone); + } + }, + + _create: function () { + var options = this.options; + // Initialize options set via HTML5 data-attributes: + $.extend(options, $(this.element[0].cloneNode(false)).data()); + options.namespace = options.namespace || this.widgetName; + this._initSpecialOptions(); + this._slots = []; + this._sequence = this._getXHRPromise(true); + this._sending = this._active = this._loaded = this._total = 0; + this._initEventHandlers(); + }, + + destroy: function () { + this._destroyEventHandlers(); + $.Widget.prototype.destroy.call(this); + }, + + enable: function () { + $.Widget.prototype.enable.call(this); + this._initEventHandlers(); + }, + + disable: function () { + this._destroyEventHandlers(); + $.Widget.prototype.disable.call(this); + }, + + // This method is exposed to the widget API and allows adding files + // using the fileupload API. The data parameter accepts an object which + // must have a files property and can contain additional options: + // .fileupload('add', {files: filesList}); + add: function (data) { + if (!data || this.options.disabled) { + return; + } + if (data.fileInput && !data.files) { + data.files = this._getFileInputFiles(data.fileInput); + } else { + data.files = $.each($.makeArray(data.files), this._normalizeFile); + } + this._onAdd(null, data); + }, + + // This method is exposed to the widget API and allows sending files + // using the fileupload API. The data parameter accepts an object which + // must have a files property and can contain additional options: + // .fileupload('send', {files: filesList}); + // The method returns a Promise object for the file upload call. + send: function (data) { + if (data && !this.options.disabled) { + if (data.fileInput && !data.files) { + data.files = this._getFileInputFiles(data.fileInput); + } else { + data.files = $.each($.makeArray(data.files), this._normalizeFile); + } + if (data.files.length) { + return this._onSend(null, data); + } + } + return this._getXHRPromise(false, data && data.context); + } + + }); + +})); diff --git a/vendors/jquery-file-upload/js/jquery.iframe-transport.js b/vendors/jquery-file-upload/js/jquery.iframe-transport.js new file mode 100644 index 000000000..04a566230 --- /dev/null +++ b/vendors/jquery-file-upload/js/jquery.iframe-transport.js @@ -0,0 +1,171 @@ +/* + * jQuery Iframe Transport Plugin 1.4 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint unparam: true, nomen: true */ +/*global define, window, document */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define(['jquery'], factory); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + // Helper variable to create unique names for the transport iframes: + var counter = 0; + + // The iframe transport accepts three additional options: + // options.fileInput: a jQuery collection of file input fields + // options.paramName: the parameter name for the file form data, + // overrides the name property of the file input field(s), + // can be a string or an array of strings. + // options.formData: an array of objects with name and value properties, + // equivalent to the return data of .serializeArray(), e.g.: + // [{name: 'a', value: 1}, {name: 'b', value: 2}] + $.ajaxTransport('iframe', function (options) { + if (options.async && (options.type === 'POST' || options.type === 'GET')) { + var form, + iframe; + return { + send: function (_, completeCallback) { + form = $('<form style="display:none;"></form>'); + // javascript:false as initial iframe src + // prevents warning popups on HTTPS in IE6. + // IE versions below IE8 cannot set the name property of + // elements that have already been added to the DOM, + // so we set the name along with the iframe HTML markup: + iframe = $( + '<iframe src="javascript:false;" name="iframe-transport-' + + (counter += 1) + '"></iframe>' + ).bind('load', function () { + var fileInputClones, + paramNames = $.isArray(options.paramName) ? + options.paramName : [options.paramName]; + iframe + .unbind('load') + .bind('load', function () { + var response; + // Wrap in a try/catch block to catch exceptions thrown + // when trying to access cross-domain iframe contents: + try { + response = iframe.contents(); + // Google Chrome and Firefox do not throw an + // exception when calling iframe.contents() on + // cross-domain requests, so we unify the response: + if (!response.length || !response[0].firstChild) { + throw new Error(); + } + } catch (e) { + response = undefined; + } + // The complete callback returns the + // iframe content document as response object: + completeCallback( + 200, + 'success', + {'iframe': response} + ); + // Fix for IE endless progress bar activity bug + // (happens on form submits to iframe targets): + $('<iframe src="javascript:false;"></iframe>') + .appendTo(form); + form.remove(); + }); + form + .prop('target', iframe.prop('name')) + .prop('action', options.url) + .prop('method', options.type); + if (options.formData) { + $.each(options.formData, function (index, field) { + $('<input type="hidden"/>') + .prop('name', field.name) + .val(field.value) + .appendTo(form); + }); + } + if (options.fileInput && options.fileInput.length && + options.type === 'POST') { + fileInputClones = options.fileInput.clone(); + // Insert a clone for each file input field: + options.fileInput.after(function (index) { + return fileInputClones[index]; + }); + if (options.paramName) { + options.fileInput.each(function (index) { + $(this).prop( + 'name', + paramNames[index] || options.paramName + ); + }); + } + // Appending the file input fields to the hidden form + // removes them from their original location: + form + .append(options.fileInput) + .prop('enctype', 'multipart/form-data') + // enctype must be set as encoding for IE: + .prop('encoding', 'multipart/form-data'); + } + form.submit(); + // Insert the file input fields at their original location + // by replacing the clones with the originals: + if (fileInputClones && fileInputClones.length) { + options.fileInput.each(function (index, input) { + var clone = $(fileInputClones[index]); + $(input).prop('name', clone.prop('name')); + clone.replaceWith(input); + }); + } + }); + form.append(iframe).appendTo(document.body); + }, + abort: function () { + if (iframe) { + // javascript:false as iframe src aborts the request + // and prevents warning popups on HTTPS in IE6. + // concat is used to avoid the "Script URL" JSLint error: + iframe + .unbind('load') + .prop('src', 'javascript'.concat(':false;')); + } + if (form) { + form.remove(); + } + } + }; + } + }); + + // The iframe transport returns the iframe content document as response. + // The following adds converters from iframe to text, json, html, and script: + $.ajaxSetup({ + converters: { + 'iframe text': function (iframe) { + return $(iframe[0].body).text(); + }, + 'iframe json': function (iframe) { + return $.parseJSON($(iframe[0].body).text()); + }, + 'iframe html': function (iframe) { + return $(iframe[0].body).html(); + }, + 'iframe script': function (iframe) { + return $.globalEval($(iframe[0].body).text()); + } + } + }); + +})); diff --git a/vendors/jquery-file-upload/js/locale.js b/vendors/jquery-file-upload/js/locale.js new file mode 100644 index 000000000..ea64b0a87 --- /dev/null +++ b/vendors/jquery-file-upload/js/locale.js @@ -0,0 +1,29 @@ +/* + * jQuery File Upload Plugin Localization Example 6.5.1 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2012, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*global window */ + +window.locale = { + "fileupload": { + "errors": { + "maxFileSize": "File is too big", + "minFileSize": "File is too small", + "acceptFileTypes": "Filetype not allowed", + "maxNumberOfFiles": "Max number of files exceeded", + "uploadedBytes": "Uploaded bytes exceed file size", + "emptyResult": "Empty file upload result" + }, + "error": "Error", + "start": "Start", + "cancel": "Cancel", + "destroy": "Delete" + } +}; diff --git a/vendors/jquery-file-upload/js/main.js b/vendors/jquery-file-upload/js/main.js new file mode 100644 index 000000000..67109588d --- /dev/null +++ b/vendors/jquery-file-upload/js/main.js @@ -0,0 +1,93 @@ +/* + * jQuery File Upload Plugin JS Example 6.7 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true, regexp: true */ +/*global $, window, document */ + +$(function () { + 'use strict'; + + // Initialize the jQuery File Upload widget: + $('#fileupload').fileupload(); + + // Enable iframe cross-domain access via redirect option: + $('#fileupload').fileupload( + 'option', + 'redirect', + window.location.href.replace( + /\/[^\/]*$/, + '/cors/result.html?%s' + ) + ); + + if (window.location.hostname === 'blueimp.github.com') { + // Demo settings: + $('#fileupload').fileupload('option', { + url: '//jquery-file-upload.appspot.com/', + maxFileSize: 5000000, + acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, + process: [ + { + action: 'load', + fileTypes: /^image\/(gif|jpeg|png)$/, + maxFileSize: 20000000 // 20MB + }, + { + action: 'resize', + maxWidth: 1440, + maxHeight: 900 + }, + { + action: 'save' + } + ] + }); + // Upload server status check for browsers with CORS support: + if ($.support.cors) { + $.ajax({ + url: '//jquery-file-upload.appspot.com/', + type: 'HEAD' + }).fail(function () { + $('<span class="alert alert-error"/>') + .text('Upload server currently unavailable - ' + + new Date()) + .appendTo('#fileupload'); + }); + } + } else { + // Load existing files: + $('#fileupload').each(function () { + var that = this; + $.getJSON(this.action, function (result) { + if (result && result.length) { + $(that).fileupload('option', 'done') + .call(that, null, {result: result}); + } + }); + }); + } + + // Initialize the Image Gallery widget: + $('#fileupload .files').imagegallery(); + + // Initialize the theme switcher: + $('#theme-switcher').change(function () { + var theme = $('#theme'); + theme.prop( + 'href', + theme.prop('href').replace( + /[\w\-]+\/jquery-ui.css/, + $(this).val() + '/jquery-ui.css' + ) + ); + }); + +}); diff --git a/vendors/jquery-file-upload/js/vendor/canvas-to-blob.min.js b/vendors/jquery-file-upload/js/vendor/canvas-to-blob.min.js new file mode 100644 index 000000000..9328aae30 --- /dev/null +++ b/vendors/jquery-file-upload/js/vendor/canvas-to-blob.min.js @@ -0,0 +1 @@ +(function(a){"use strict";var b=a.HTMLCanvasElement&&a.HTMLCanvasElement.prototype,c=a.BlobBuilder||a.WebKitBlobBuilder||a.MozBlobBuilder||a.MSBlobBuilder,d=c&&a.atob&&a.ArrayBuffer&&a.Uint8Array&&function(a){var b,d,e,f,g,h;a.split(",")[0].indexOf("base64")>=0?b=atob(a.split(",")[1]):b=decodeURIComponent(a.split(",")[1]),d=new ArrayBuffer(b.length),e=new Uint8Array(d);for(f=0;f<b.length;f+=1)e[f]=b.charCodeAt(f);return g=new c,g.append(d),h=a.split(",")[0].split(":")[1].split(";")[0],g.getBlob(h)};a.HTMLCanvasElement&&!b.toBlob&&(b.mozGetAsFile?b.toBlob=function(a,b){a(this.mozGetAsFile("blob",b))}:b.toDataURL&&d&&(b.toBlob=function(a,b){a(d(this.toDataURL(b)))})),typeof define!="undefined"&&define.amd?define(function(){return d}):a.dataURLtoBlob=d})(this);
\ No newline at end of file diff --git a/vendors/jquery-file-upload/js/vendor/jquery.ui.widget.js b/vendors/jquery-file-upload/js/vendor/jquery.ui.widget.js new file mode 100644 index 000000000..b980122a3 --- /dev/null +++ b/vendors/jquery-file-upload/js/vendor/jquery.ui.widget.js @@ -0,0 +1,282 @@ +/* + * jQuery UI Widget 1.8.22+amd + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Widget + */ + +(function (factory) { + if (typeof define === "function" && define.amd) { + // Register as an anonymous AMD module: + define(["jquery"], factory); + } else { + // Browser globals: + factory(jQuery); + } +}(function( $, undefined ) { + +// jQuery 1.4+ +if ( $.cleanData ) { + var _cleanData = $.cleanData; + $.cleanData = function( elems ) { + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + try { + $( elem ).triggerHandler( "remove" ); + // http://bugs.jquery.com/ticket/8235 + } catch( e ) {} + } + _cleanData( elems ); + }; +} else { + var _remove = $.fn.remove; + $.fn.remove = function( selector, keepData ) { + return this.each(function() { + if ( !keepData ) { + if ( !selector || $.filter( selector, [ this ] ).length ) { + $( "*", this ).add( [ this ] ).each(function() { + try { + $( this ).triggerHandler( "remove" ); + // http://bugs.jquery.com/ticket/8235 + } catch( e ) {} + }); + } + } + return _remove.call( $(this), selector, keepData ); + }); + }; +} + +$.widget = function( name, base, prototype ) { + var namespace = name.split( "." )[ 0 ], + fullName; + name = name.split( "." )[ 1 ]; + fullName = namespace + "-" + name; + + if ( !prototype ) { + prototype = base; + base = $.Widget; + } + + // create selector for plugin + $.expr[ ":" ][ fullName ] = function( elem ) { + return !!$.data( elem, name ); + }; + + $[ namespace ] = $[ namespace ] || {}; + $[ namespace ][ name ] = function( options, element ) { + // allow instantiation without initializing for simple inheritance + if ( arguments.length ) { + this._createWidget( options, element ); + } + }; + + var basePrototype = new base(); + // we need to make the options hash a property directly on the new instance + // otherwise we'll modify the options hash on the prototype that we're + // inheriting from +// $.each( basePrototype, function( key, val ) { +// if ( $.isPlainObject(val) ) { +// basePrototype[ key ] = $.extend( {}, val ); +// } +// }); + basePrototype.options = $.extend( true, {}, basePrototype.options ); + $[ namespace ][ name ].prototype = $.extend( true, basePrototype, { + namespace: namespace, + widgetName: name, + widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name, + widgetBaseClass: fullName + }, prototype ); + + $.widget.bridge( name, $[ namespace ][ name ] ); +}; + +$.widget.bridge = function( name, object ) { + $.fn[ name ] = function( options ) { + var isMethodCall = typeof options === "string", + args = Array.prototype.slice.call( arguments, 1 ), + returnValue = this; + + // allow multiple hashes to be passed on init + options = !isMethodCall && args.length ? + $.extend.apply( null, [ true, options ].concat(args) ) : + options; + + // prevent calls to internal methods + if ( isMethodCall && options.charAt( 0 ) === "_" ) { + return returnValue; + } + + if ( isMethodCall ) { + this.each(function() { + var instance = $.data( this, name ), + methodValue = instance && $.isFunction( instance[options] ) ? + instance[ options ].apply( instance, args ) : + instance; + // TODO: add this back in 1.9 and use $.error() (see #5972) +// if ( !instance ) { +// throw "cannot call methods on " + name + " prior to initialization; " + +// "attempted to call method '" + options + "'"; +// } +// if ( !$.isFunction( instance[options] ) ) { +// throw "no such method '" + options + "' for " + name + " widget instance"; +// } +// var methodValue = instance[ options ].apply( instance, args ); + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue; + return false; + } + }); + } else { + this.each(function() { + var instance = $.data( this, name ); + if ( instance ) { + instance.option( options || {} )._init(); + } else { + $.data( this, name, new object( options, this ) ); + } + }); + } + + return returnValue; + }; +}; + +$.Widget = function( options, element ) { + // allow instantiation without initializing for simple inheritance + if ( arguments.length ) { + this._createWidget( options, element ); + } +}; + +$.Widget.prototype = { + widgetName: "widget", + widgetEventPrefix: "", + options: { + disabled: false + }, + _createWidget: function( options, element ) { + // $.widget.bridge stores the plugin instance, but we do it anyway + // so that it's stored even before the _create function runs + $.data( element, this.widgetName, this ); + this.element = $( element ); + this.options = $.extend( true, {}, + this.options, + this._getCreateOptions(), + options ); + + var self = this; + this.element.bind( "remove." + this.widgetName, function() { + self.destroy(); + }); + + this._create(); + this._trigger( "create" ); + this._init(); + }, + _getCreateOptions: function() { + return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ]; + }, + _create: function() {}, + _init: function() {}, + + destroy: function() { + this.element + .unbind( "." + this.widgetName ) + .removeData( this.widgetName ); + this.widget() + .unbind( "." + this.widgetName ) + .removeAttr( "aria-disabled" ) + .removeClass( + this.widgetBaseClass + "-disabled " + + "ui-state-disabled" ); + }, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key; + + if ( arguments.length === 0 ) { + // don't return a reference to the internal hash + return $.extend( {}, this.options ); + } + + if (typeof key === "string" ) { + if ( value === undefined ) { + return this.options[ key ]; + } + options = {}; + options[ key ] = value; + } + + this._setOptions( options ); + + return this; + }, + _setOptions: function( options ) { + var self = this; + $.each( options, function( key, value ) { + self._setOption( key, value ); + }); + + return this; + }, + _setOption: function( key, value ) { + this.options[ key ] = value; + + if ( key === "disabled" ) { + this.widget() + [ value ? "addClass" : "removeClass"]( + this.widgetBaseClass + "-disabled" + " " + + "ui-state-disabled" ) + .attr( "aria-disabled", value ); + } + + return this; + }, + + enable: function() { + return this._setOption( "disabled", false ); + }, + disable: function() { + return this._setOption( "disabled", true ); + }, + + _trigger: function( type, event, data ) { + var prop, orig, + callback = this.options[ type ]; + + data = data || {}; + event = $.Event( event ); + event.type = ( type === this.widgetEventPrefix ? + type : + this.widgetEventPrefix + type ).toLowerCase(); + // the original event may come from any element + // so we need to reset the target on the new event + event.target = this.element[ 0 ]; + + // copy original event properties over to the new event + orig = event.originalEvent; + if ( orig ) { + for ( prop in orig ) { + if ( !( prop in event ) ) { + event[ prop ] = orig[ prop ]; + } + } + } + + this.element.trigger( event, data ); + + return !( $.isFunction(callback) && + callback.call( this.element[0], event, data ) === false || + event.isDefaultPrevented() ); + } +}; + +})); diff --git a/vendors/jquery-file-upload/js/vendor/load-image.min.js b/vendors/jquery-file-upload/js/vendor/load-image.min.js new file mode 100644 index 000000000..0f7ab3ffe --- /dev/null +++ b/vendors/jquery-file-upload/js/vendor/load-image.min.js @@ -0,0 +1 @@ +(function(a){"use strict";var b=function(a,c,d){var e=document.createElement("img"),f,g;return e.onerror=c,e.onload=function(){g&&(!d||!d.noRevoke)&&b.revokeObjectURL(g),c(b.scale(e,d))},window.Blob&&a instanceof Blob||window.File&&a instanceof File?f=g=b.createObjectURL(a):f=a,f?(e.src=f,e):b.readFile(a,function(a){e.src=a})},c=window.createObjectURL&&window||window.URL&&URL.revokeObjectURL&&URL||window.webkitURL&&webkitURL;b.scale=function(a,b){b=b||{};var c=document.createElement("canvas"),d=a.width,e=a.height,f=Math.max((b.minWidth||d)/d,(b.minHeight||e)/e);return f>1&&(d=parseInt(d*f,10),e=parseInt(e*f,10)),f=Math.min((b.maxWidth||d)/d,(b.maxHeight||e)/e),f<1&&(d=parseInt(d*f,10),e=parseInt(e*f,10)),a.getContext||b.canvas&&c.getContext?(c.width=d,c.height=e,c.getContext("2d").drawImage(a,0,0,d,e),c):(a.width=d,a.height=e,a)},b.createObjectURL=function(a){return c?c.createObjectURL(a):!1},b.revokeObjectURL=function(a){return c?c.revokeObjectURL(a):!1},b.readFile=function(a,b){if(window.FileReader&&FileReader.prototype.readAsDataURL){var c=new FileReader;return c.onload=function(a){b(a.target.result)},c.readAsDataURL(a),c}return!1},typeof define!="undefined"&&define.amd?define(function(){return b}):a.loadImage=b})(this);
\ No newline at end of file diff --git a/vendors/jquery-file-upload/js/vendor/tmpl.min.js b/vendors/jquery-file-upload/js/vendor/tmpl.min.js new file mode 100644 index 000000000..065532e7c --- /dev/null +++ b/vendors/jquery-file-upload/js/vendor/tmpl.min.js @@ -0,0 +1 @@ +(function(a){"use strict";var b=function(a,c){var d=/[^\w\-\.:]/.test(a)?new Function(b.arg+",tmpl","var _e=tmpl.encode"+b.helper+",_s='"+a.replace(b.regexp,b.func)+"';return _s;"):b.cache[a]=b.cache[a]||b(b.load(a));return c?d(c,b):function(a){return d(a,b)}};b.cache={},b.load=function(a){return document.getElementById(a).innerHTML},b.regexp=/([\s'\\])(?![^%]*%\})|(?:\{%(=|#)([\s\S]+?)%\})|(\{%)|(%\})/g,b.func=function(a,b,c,d,e,f){if(b)return{"\n":"\\n","\r":"\\r","\t":"\\t"," ":" "}[a]||"\\"+a;if(c)return c==="="?"'+_e("+d+")+'":"'+("+d+"||'')+'";if(e)return"';";if(f)return"_s+='"},b.encReg=/[<>&"'\x00]/g,b.encMap={"<":"<",">":">","&":"&",'"':""","'":"'"},b.encode=function(a){return String(a||"").replace(b.encReg,function(a){return b.encMap[a]||""})},b.arg="o",b.helper=",print=function(s,e){_s+=e&&(s||'')||_e(s);},include=function(s,d){_s+=tmpl(s,d);}",typeof define=="function"&&define.amd?define(function(){return b}):a.tmpl=b})(this);
\ No newline at end of file diff --git a/vendors/jquery-file-upload/package.json b/vendors/jquery-file-upload/package.json new file mode 100644 index 000000000..56f9d5f25 --- /dev/null +++ b/vendors/jquery-file-upload/package.json @@ -0,0 +1,56 @@ +{ + "name": "blueimp-file-upload-jquery-ui", + "version": "6.9.2", + "title": "jQuery File Upload - jQuery UI version", + "description": "File Upload widget with multiple file selection, drag&drop support, progress bars and preview images for jQuery. Supports cross-domain, chunked and resumable file uploads and client-side image resizing. Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads.", + "keywords": [ + "jquery", + "file", + "upload", + "widget", + "multiple", + "selection", + "drag", + "drop", + "progress", + "preview", + "cross-domain", + "cross-site", + "chunk", + "resume", + "gae", + "go", + "python", + "php", + "ui" + ], + "homepage": "https://github.com/blueimp/jQuery-File-Upload", + "author": { + "name": "Sebastian Tschan", + "url": "https://blueimp.net" + }, + "maintainers": [ + { + "name": "Sebastian Tschan", + "url": "https://blueimp.net" + } + ], + "repository": { + "type": "git", + "url": "git://github.com/blueimp/jQuery-File-Upload.git" + }, + "bugs": "https://github.com/blueimp/jQuery-File-Upload/issues", + "licenses": [ + { + "type": "MIT", + "url": "http://www.opensource.org/licenses/MIT" + } + ], + "dependencies": { + "jquery": ">=1.6", + "jquery.ui": ">=1.8", + "blueimp-tmpl": ">=2.1.0", + "blueimp-load-image": ">=1.1.6", + "blueimp-canvas-to-blob": ">=2.0.0" + } +} diff --git a/vendors/jquery-file-upload/server/gae-go/app.yaml b/vendors/jquery-file-upload/server/gae-go/app.yaml new file mode 100644 index 000000000..2d09daa56 --- /dev/null +++ b/vendors/jquery-file-upload/server/gae-go/app.yaml @@ -0,0 +1,12 @@ +application: jquery-file-upload +version: 2 +runtime: go +api_version: go1 + +handlers: +- url: /(favicon\.ico|robots\.txt) + static_files: static/\1 + upload: static/(.*) + expiration: '1d' +- url: /.* + script: _go_app diff --git a/vendors/jquery-file-upload/server/gae-go/app/main.go b/vendors/jquery-file-upload/server/gae-go/app/main.go new file mode 100644 index 000000000..01dc2f204 --- /dev/null +++ b/vendors/jquery-file-upload/server/gae-go/app/main.go @@ -0,0 +1,361 @@ +/* + * jQuery File Upload Plugin GAE Go Example 2.0 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +package app + +import ( + "appengine" + "appengine/blobstore" + "appengine/memcache" + "appengine/taskqueue" + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "image" + "image/png" + "io" + "log" + "mime/multipart" + "net/http" + "net/url" + "regexp" + "resize" + "strings" + "time" +) + +import _ "image/gif" +import _ "image/jpeg" + +const ( + WEBSITE = "http://blueimp.github.com/jQuery-File-Upload/" + MIN_FILE_SIZE = 1 // bytes + MAX_FILE_SIZE = 5000000 // bytes + IMAGE_TYPES = "image/(gif|p?jpeg|(x-)?png)" + ACCEPT_FILE_TYPES = IMAGE_TYPES + EXPIRATION_TIME = 300 // seconds + THUMBNAIL_MAX_WIDTH = 80 + THUMBNAIL_MAX_HEIGHT = THUMBNAIL_MAX_WIDTH +) + +var ( + imageTypes = regexp.MustCompile(IMAGE_TYPES) + acceptFileTypes = regexp.MustCompile(ACCEPT_FILE_TYPES) +) + +type FileInfo struct { + Key appengine.BlobKey `json:"-"` + Url string `json:"url,omitempty"` + ThumbnailUrl string `json:"thumbnail_url,omitempty"` + Name string `json:"name"` + Type string `json:"type"` + Size int64 `json:"size"` + Error string `json:"error,omitempty"` + DeleteUrl string `json:"delete_url,omitempty"` + DeleteType string `json:"delete_type,omitempty"` +} + +func (fi *FileInfo) ValidateType() (valid bool) { + if acceptFileTypes.MatchString(fi.Type) { + return true + } + fi.Error = "acceptFileTypes" + return false +} + +func (fi *FileInfo) ValidateSize() (valid bool) { + if fi.Size < MIN_FILE_SIZE { + fi.Error = "minFileSize" + } else if fi.Size > MAX_FILE_SIZE { + fi.Error = "maxFileSize" + } else { + return true + } + return false +} + +func (fi *FileInfo) CreateUrls(r *http.Request, c appengine.Context) { + u := &url.URL{ + Scheme: r.URL.Scheme, + Host: appengine.DefaultVersionHostname(c), + Path: "/", + } + uString := u.String() + fi.Url = uString + escape(string(fi.Key)) + "/" + + escape(string(fi.Name)) + fi.DeleteUrl = fi.Url + fi.DeleteType = "DELETE" + if fi.ThumbnailUrl != "" && -1 == strings.Index( + r.Header.Get("Accept"), + "application/json", + ) { + fi.ThumbnailUrl = uString + "thumbnails/" + + escape(string(fi.Key)) + } +} + +func (fi *FileInfo) CreateThumbnail(r io.Reader, c appengine.Context) (data []byte, err error) { + defer func() { + if rec := recover(); rec != nil { + log.Println(rec) + // 1x1 pixel transparent GIf, bas64 encoded: + s := "R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" + data, _ = base64.StdEncoding.DecodeString(s) + fi.ThumbnailUrl = "data:image/gif;base64," + s + } + memcache.Add(c, &memcache.Item{ + Key: string(fi.Key), + Value: data, + Expiration: EXPIRATION_TIME, + }) + }() + img, _, err := image.Decode(r) + check(err) + if bounds := img.Bounds(); bounds.Dx() > THUMBNAIL_MAX_WIDTH || + bounds.Dy() > THUMBNAIL_MAX_HEIGHT { + w, h := THUMBNAIL_MAX_WIDTH, THUMBNAIL_MAX_HEIGHT + if bounds.Dx() > bounds.Dy() { + h = bounds.Dy() * h / bounds.Dx() + } else { + w = bounds.Dx() * w / bounds.Dy() + } + img = resize.Resize(img, img.Bounds(), w, h) + } + var b bytes.Buffer + err = png.Encode(&b, img) + check(err) + data = b.Bytes() + fi.ThumbnailUrl = "data:image/png;base64," + + base64.StdEncoding.EncodeToString(data) + return +} + +func check(err error) { + if err != nil { + panic(err) + } +} + +func escape(s string) string { + return strings.Replace(url.QueryEscape(s), "+", "%20", -1) +} + +func delayedDelete(c appengine.Context, fi *FileInfo) { + if key := string(fi.Key); key != "" { + task := &taskqueue.Task{ + Path: "/" + escape(key) + "/-", + Method: "DELETE", + Delay: time.Duration(EXPIRATION_TIME) * time.Second, + } + taskqueue.Add(c, task, "") + } +} + +func handleUpload(r *http.Request, p *multipart.Part) (fi *FileInfo) { + fi = &FileInfo{ + Name: p.FileName(), + Type: p.Header.Get("Content-Type"), + } + if !fi.ValidateType() { + return + } + defer func() { + if rec := recover(); rec != nil { + log.Println(rec) + fi.Error = rec.(error).Error() + } + }() + var b bytes.Buffer + lr := &io.LimitedReader{R: p, N: MAX_FILE_SIZE + 1} + context := appengine.NewContext(r) + w, err := blobstore.Create(context, fi.Type) + defer func() { + w.Close() + fi.Size = MAX_FILE_SIZE + 1 - lr.N + fi.Key, err = w.Key() + check(err) + if !fi.ValidateSize() { + err := blobstore.Delete(context, fi.Key) + check(err) + return + } + delayedDelete(context, fi) + if b.Len() > 0 { + fi.CreateThumbnail(&b, context) + } + fi.CreateUrls(r, context) + }() + check(err) + var wr io.Writer = w + if imageTypes.MatchString(fi.Type) { + wr = io.MultiWriter(&b, w) + } + _, err = io.Copy(wr, lr) + return +} + +func getFormValue(p *multipart.Part) string { + var b bytes.Buffer + io.CopyN(&b, p, int64(1<<20)) // Copy max: 1 MiB + return b.String() +} + +func handleUploads(r *http.Request) (fileInfos []*FileInfo) { + fileInfos = make([]*FileInfo, 0) + mr, err := r.MultipartReader() + check(err) + r.Form, err = url.ParseQuery(r.URL.RawQuery) + check(err) + part, err := mr.NextPart() + for err == nil { + if name := part.FormName(); name != "" { + if part.FileName() != "" { + fileInfos = append(fileInfos, handleUpload(r, part)) + } else { + r.Form[name] = append(r.Form[name], getFormValue(part)) + } + } + part, err = mr.NextPart() + } + return +} + +func get(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" { + http.Redirect(w, r, WEBSITE, http.StatusFound) + return + } + parts := strings.Split(r.URL.Path, "/") + if len(parts) == 3 { + if key := parts[1]; key != "" { + blobKey := appengine.BlobKey(key) + bi, err := blobstore.Stat(appengine.NewContext(r), blobKey) + if err == nil { + w.Header().Add( + "Cache-Control", + fmt.Sprintf("public,max-age=%d", EXPIRATION_TIME), + ) + if imageTypes.MatchString(bi.ContentType) { + w.Header().Add("X-Content-Type-Options", "nosniff") + } else { + w.Header().Add("Content-Type", "application/octet-stream") + w.Header().Add( + "Content-Disposition:", + fmt.Sprintf("attachment; filename=%s;", parts[2]), + ) + } + blobstore.Send(w, appengine.BlobKey(key)) + return + } + } + } + http.Error(w, "404 Not Found", http.StatusNotFound) +} + +func post(w http.ResponseWriter, r *http.Request) { + b, err := json.Marshal(handleUploads(r)) + check(err) + if redirect := r.FormValue("redirect"); redirect != "" { + http.Redirect(w, r, fmt.Sprintf( + redirect, + escape(string(b)), + ), http.StatusFound) + return + } + jsonType := "application/json" + if strings.Index(r.Header.Get("Accept"), jsonType) != -1 { + w.Header().Set("Content-Type", jsonType) + } + fmt.Fprintln(w, string(b)) +} + +func delete(w http.ResponseWriter, r *http.Request) { + parts := strings.Split(r.URL.Path, "/") + if len(parts) != 3 { + return + } + if key := parts[1]; key != "" { + c := appengine.NewContext(r) + blobstore.Delete(c, appengine.BlobKey(key)) + memcache.Delete(c, key) + } +} + +func serveThumbnail(w http.ResponseWriter, r *http.Request) { + parts := strings.Split(r.URL.Path, "/") + if len(parts) == 3 { + if key := parts[2]; key != "" { + var data []byte + c := appengine.NewContext(r) + item, err := memcache.Get(c, key) + if err == nil { + data = item.Value + } else { + blobKey := appengine.BlobKey(key) + if _, err = blobstore.Stat(c, blobKey); err == nil { + fi := FileInfo{Key: blobKey} + data, _ = fi.CreateThumbnail( + blobstore.NewReader(c, blobKey), + c, + ) + } + } + if err == nil && len(data) > 3 { + w.Header().Add( + "Cache-Control", + fmt.Sprintf("public,max-age=%d", EXPIRATION_TIME), + ) + contentType := "image/png" + if string(data[:3]) == "GIF" { + contentType = "image/gif" + } else if string(data[1:4]) != "PNG" { + contentType = "image/jpeg" + } + w.Header().Set("Content-Type", contentType) + fmt.Fprintln(w, string(data)) + return + } + } + } + http.Error(w, "404 Not Found", http.StatusNotFound) +} + +func handle(w http.ResponseWriter, r *http.Request) { + params, err := url.ParseQuery(r.URL.RawQuery) + check(err) + w.Header().Add("Access-Control-Allow-Origin", "*") + w.Header().Add( + "Access-Control-Allow-Methods", + "OPTIONS, HEAD, GET, POST, PUT, DELETE", + ) + switch r.Method { + case "OPTIONS": + case "HEAD": + case "GET": + get(w, r) + case "POST": + if len(params["_method"]) > 0 && params["_method"][0] == "DELETE" { + delete(w, r) + } else { + post(w, r) + } + case "DELETE": + delete(w, r) + default: + http.Error(w, "501 Not Implemented", http.StatusNotImplemented) + } +} + +func init() { + http.HandleFunc("/", handle) + http.HandleFunc("/thumbnails/", serveThumbnail) +} diff --git a/vendors/jquery-file-upload/server/gae-go/resize/resize.go b/vendors/jquery-file-upload/server/gae-go/resize/resize.go new file mode 100644 index 000000000..dcb627870 --- /dev/null +++ b/vendors/jquery-file-upload/server/gae-go/resize/resize.go @@ -0,0 +1,247 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package resize + +import ( + "image" + "image/color" +) + +// Resize returns a scaled copy of the image slice r of m. +// The returned image has width w and height h. +func Resize(m image.Image, r image.Rectangle, w, h int) image.Image { + if w < 0 || h < 0 { + return nil + } + if w == 0 || h == 0 || r.Dx() <= 0 || r.Dy() <= 0 { + return image.NewRGBA64(image.Rect(0, 0, w, h)) + } + switch m := m.(type) { + case *image.RGBA: + return resizeRGBA(m, r, w, h) + case *image.YCbCr: + if m, ok := resizeYCbCr(m, r, w, h); ok { + return m + } + } + ww, hh := uint64(w), uint64(h) + dx, dy := uint64(r.Dx()), uint64(r.Dy()) + // The scaling algorithm is to nearest-neighbor magnify the dx * dy source + // to a (ww*dx) * (hh*dy) intermediate image and then minify the intermediate + // image back down to a ww * hh destination with a simple box filter. + // The intermediate image is implied, we do not physically allocate a slice + // of length ww*dx*hh*dy. + // For example, consider a 4*3 source image. Label its pixels from a-l: + // abcd + // efgh + // ijkl + // To resize this to a 3*2 destination image, the intermediate is 12*6. + // Whitespace has been added to delineate the destination pixels: + // aaab bbcc cddd + // aaab bbcc cddd + // eeef ffgg ghhh + // + // eeef ffgg ghhh + // iiij jjkk klll + // iiij jjkk klll + // Thus, the 'b' source pixel contributes one third of its value to the + // (0, 0) destination pixel and two thirds to (1, 0). + // The implementation is a two-step process. First, the source pixels are + // iterated over and each source pixel's contribution to 1 or more + // destination pixels are summed. Second, the sums are divided by a scaling + // factor to yield the destination pixels. + // TODO: By interleaving the two steps, instead of doing all of + // step 1 first and all of step 2 second, we could allocate a smaller sum + // slice of length 4*w*2 instead of 4*w*h, although the resultant code + // would become more complicated. + n, sum := dx*dy, make([]uint64, 4*w*h) + for y := r.Min.Y; y < r.Max.Y; y++ { + for x := r.Min.X; x < r.Max.X; x++ { + // Get the source pixel. + r32, g32, b32, a32 := m.At(x, y).RGBA() + r64 := uint64(r32) + g64 := uint64(g32) + b64 := uint64(b32) + a64 := uint64(a32) + // Spread the source pixel over 1 or more destination rows. + py := uint64(y) * hh + for remy := hh; remy > 0; { + qy := dy - (py % dy) + if qy > remy { + qy = remy + } + // Spread the source pixel over 1 or more destination columns. + px := uint64(x) * ww + index := 4 * ((py/dy)*ww + (px / dx)) + for remx := ww; remx > 0; { + qx := dx - (px % dx) + if qx > remx { + qx = remx + } + sum[index+0] += r64 * qx * qy + sum[index+1] += g64 * qx * qy + sum[index+2] += b64 * qx * qy + sum[index+3] += a64 * qx * qy + index += 4 + px += qx + remx -= qx + } + py += qy + remy -= qy + } + } + } + return average(sum, w, h, n*0x0101) +} + +// average convert the sums to averages and returns the result. +func average(sum []uint64, w, h int, n uint64) image.Image { + ret := image.NewRGBA(image.Rect(0, 0, w, h)) + for y := 0; y < h; y++ { + for x := 0; x < w; x++ { + index := 4 * (y*w + x) + ret.SetRGBA(x, y, color.RGBA{ + uint8(sum[index+0] / n), + uint8(sum[index+1] / n), + uint8(sum[index+2] / n), + uint8(sum[index+3] / n), + }) + } + } + return ret +} + +// resizeYCbCr returns a scaled copy of the YCbCr image slice r of m. +// The returned image has width w and height h. +func resizeYCbCr(m *image.YCbCr, r image.Rectangle, w, h int) (image.Image, bool) { + var verticalRes int + switch m.SubsampleRatio { + case image.YCbCrSubsampleRatio420: + verticalRes = 2 + case image.YCbCrSubsampleRatio422: + verticalRes = 1 + default: + return nil, false + } + ww, hh := uint64(w), uint64(h) + dx, dy := uint64(r.Dx()), uint64(r.Dy()) + // See comment in Resize. + n, sum := dx*dy, make([]uint64, 4*w*h) + for y := r.Min.Y; y < r.Max.Y; y++ { + Y := m.Y[y*m.YStride:] + Cb := m.Cb[y/verticalRes*m.CStride:] + Cr := m.Cr[y/verticalRes*m.CStride:] + for x := r.Min.X; x < r.Max.X; x++ { + // Get the source pixel. + r8, g8, b8 := color.YCbCrToRGB(Y[x], Cb[x/2], Cr[x/2]) + r64 := uint64(r8) + g64 := uint64(g8) + b64 := uint64(b8) + // Spread the source pixel over 1 or more destination rows. + py := uint64(y) * hh + for remy := hh; remy > 0; { + qy := dy - (py % dy) + if qy > remy { + qy = remy + } + // Spread the source pixel over 1 or more destination columns. + px := uint64(x) * ww + index := 4 * ((py/dy)*ww + (px / dx)) + for remx := ww; remx > 0; { + qx := dx - (px % dx) + if qx > remx { + qx = remx + } + qxy := qx * qy + sum[index+0] += r64 * qxy + sum[index+1] += g64 * qxy + sum[index+2] += b64 * qxy + sum[index+3] += 0xFFFF * qxy + index += 4 + px += qx + remx -= qx + } + py += qy + remy -= qy + } + } + } + return average(sum, w, h, n), true +} + +// resizeRGBA returns a scaled copy of the RGBA image slice r of m. +// The returned image has width w and height h. +func resizeRGBA(m *image.RGBA, r image.Rectangle, w, h int) image.Image { + ww, hh := uint64(w), uint64(h) + dx, dy := uint64(r.Dx()), uint64(r.Dy()) + // See comment in Resize. + n, sum := dx*dy, make([]uint64, 4*w*h) + for y := r.Min.Y; y < r.Max.Y; y++ { + pixOffset := m.PixOffset(r.Min.X, y) + for x := r.Min.X; x < r.Max.X; x++ { + // Get the source pixel. + r64 := uint64(m.Pix[pixOffset+0]) + g64 := uint64(m.Pix[pixOffset+1]) + b64 := uint64(m.Pix[pixOffset+2]) + a64 := uint64(m.Pix[pixOffset+3]) + pixOffset += 4 + // Spread the source pixel over 1 or more destination rows. + py := uint64(y) * hh + for remy := hh; remy > 0; { + qy := dy - (py % dy) + if qy > remy { + qy = remy + } + // Spread the source pixel over 1 or more destination columns. + px := uint64(x) * ww + index := 4 * ((py/dy)*ww + (px / dx)) + for remx := ww; remx > 0; { + qx := dx - (px % dx) + if qx > remx { + qx = remx + } + qxy := qx * qy + sum[index+0] += r64 * qxy + sum[index+1] += g64 * qxy + sum[index+2] += b64 * qxy + sum[index+3] += a64 * qxy + index += 4 + px += qx + remx -= qx + } + py += qy + remy -= qy + } + } + } + return average(sum, w, h, n) +} + +// Resample returns a resampled copy of the image slice r of m. +// The returned image has width w and height h. +func Resample(m image.Image, r image.Rectangle, w, h int) image.Image { + if w < 0 || h < 0 { + return nil + } + if w == 0 || h == 0 || r.Dx() <= 0 || r.Dy() <= 0 { + return image.NewRGBA64(image.Rect(0, 0, w, h)) + } + curw, curh := r.Dx(), r.Dy() + img := image.NewRGBA(image.Rect(0, 0, w, h)) + for y := 0; y < h; y++ { + for x := 0; x < w; x++ { + // Get a source pixel. + subx := x * curw / w + suby := y * curh / h + r32, g32, b32, a32 := m.At(subx, suby).RGBA() + r := uint8(r32 >> 8) + g := uint8(g32 >> 8) + b := uint8(b32 >> 8) + a := uint8(a32 >> 8) + img.SetRGBA(x, y, color.RGBA{r, g, b, a}) + } + } + return img +} diff --git a/vendors/jquery-file-upload/server/gae-go/static/favicon.ico b/vendors/jquery-file-upload/server/gae-go/static/favicon.ico Binary files differnew file mode 100644 index 000000000..1a71ea772 --- /dev/null +++ b/vendors/jquery-file-upload/server/gae-go/static/favicon.ico diff --git a/vendors/jquery-file-upload/server/gae-go/static/robots.txt b/vendors/jquery-file-upload/server/gae-go/static/robots.txt new file mode 100644 index 000000000..eb0536286 --- /dev/null +++ b/vendors/jquery-file-upload/server/gae-go/static/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/vendors/jquery-file-upload/server/gae-python/app.yaml b/vendors/jquery-file-upload/server/gae-python/app.yaml new file mode 100644 index 000000000..5fe123f59 --- /dev/null +++ b/vendors/jquery-file-upload/server/gae-python/app.yaml @@ -0,0 +1,16 @@ +application: jquery-file-upload +version: 1 +runtime: python27 +api_version: 1 +threadsafe: true + +builtins: +- deferred: on + +handlers: +- url: /(favicon\.ico|robots\.txt) + static_files: static/\1 + upload: static/(.*) + expiration: '1d' +- url: /.* + script: main.app diff --git a/vendors/jquery-file-upload/server/gae-python/main.py b/vendors/jquery-file-upload/server/gae-python/main.py new file mode 100644 index 000000000..37218c827 --- /dev/null +++ b/vendors/jquery-file-upload/server/gae-python/main.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +# +# jQuery File Upload Plugin GAE Python Example 1.1.4 +# https://github.com/blueimp/jQuery-File-Upload +# +# Copyright 2011, Sebastian Tschan +# https://blueimp.net +# +# Licensed under the MIT license: +# http://www.opensource.org/licenses/MIT +# + +from __future__ import with_statement +from google.appengine.api import files, images +from google.appengine.ext import blobstore, deferred +from google.appengine.ext.webapp import blobstore_handlers +import json, re, urllib, webapp2 + +WEBSITE = 'http://blueimp.github.com/jQuery-File-Upload/' +MIN_FILE_SIZE = 1 # bytes +MAX_FILE_SIZE = 5000000 # bytes +IMAGE_TYPES = re.compile('image/(gif|p?jpeg|(x-)?png)') +ACCEPT_FILE_TYPES = IMAGE_TYPES +THUMBNAIL_MODIFICATOR = '=s80' # max width / height +EXPIRATION_TIME = 300 # seconds + +def cleanup(blob_keys): + blobstore.delete(blob_keys) + +class UploadHandler(webapp2.RequestHandler): + + def initialize(self, request, response): + super(UploadHandler, self).initialize(request, response) + self.response.headers['Access-Control-Allow-Origin'] = '*' + self.response.headers[ + 'Access-Control-Allow-Methods' + ] = 'OPTIONS, HEAD, GET, POST, PUT, DELETE' + + def validate(self, file): + if file['size'] < MIN_FILE_SIZE: + file['error'] = 'minFileSize' + elif file['size'] > MAX_FILE_SIZE: + file['error'] = 'maxFileSize' + elif not ACCEPT_FILE_TYPES.match(file['type']): + file['error'] = 'acceptFileTypes' + else: + return True + return False + + def get_file_size(self, file): + file.seek(0, 2) # Seek to the end of the file + size = file.tell() # Get the position of EOF + file.seek(0) # Reset the file position to the beginning + return size + + def write_blob(self, data, info): + blob = files.blobstore.create( + mime_type=info['type'], + _blobinfo_uploaded_filename=info['name'] + ) + with files.open(blob, 'a') as f: + f.write(data) + files.finalize(blob) + return files.blobstore.get_blob_key(blob) + + def handle_upload(self): + results = [] + blob_keys = [] + for name, fieldStorage in self.request.POST.items(): + if type(fieldStorage) is unicode: + continue + result = {} + result['name'] = re.sub(r'^.*\\', '', + fieldStorage.filename) + result['type'] = fieldStorage.type + result['size'] = self.get_file_size(fieldStorage.file) + if self.validate(result): + blob_key = str( + self.write_blob(fieldStorage.value, result) + ) + blob_keys.append(blob_key) + result['delete_type'] = 'DELETE' + result['delete_url'] = self.request.host_url +\ + '/?key=' + urllib.quote(blob_key, '') + if (IMAGE_TYPES.match(result['type'])): + try: + result['url'] = images.get_serving_url( + blob_key, + secure_url=self.request.host_url\ + .startswith('https') + ) + result['thumbnail_url'] = result['url'] +\ + THUMBNAIL_MODIFICATOR + except: # Could not get an image serving url + pass + if not 'url' in result: + result['url'] = self.request.host_url +\ + '/' + blob_key + '/' + urllib.quote( + result['name'].encode('utf-8'), '') + results.append(result) + deferred.defer( + cleanup, + blob_keys, + _countdown=EXPIRATION_TIME + ) + return results + + def options(self): + pass + + def head(self): + pass + + def get(self): + self.redirect(WEBSITE) + + def post(self): + if (self.request.get('_method') == 'DELETE'): + return self.delete() + s = json.dumps(self.handle_upload(), separators=(',',':')) + redirect = self.request.get('redirect') + if redirect: + return self.redirect(str( + redirect.replace('%s', urllib.quote(s, ''), 1) + )) + if 'application/json' in self.request.headers.get('Accept'): + self.response.headers['Content-Type'] = 'application/json' + self.response.write(s) + + def delete(self): + blobstore.delete(self.request.get('key') or '') + +class DownloadHandler(blobstore_handlers.BlobstoreDownloadHandler): + def get(self, key, filename): + if not blobstore.get(key): + self.error(404) + else: + # Cache for the expiration time: + self.response.headers['Cache-Control'] =\ + 'public,max-age=%d' % EXPIRATION_TIME + self.send_blob(key, save_as=filename) + +app = webapp2.WSGIApplication( + [ + ('/', UploadHandler), + ('/([^/]+)/([^/]+)', DownloadHandler) + ], + debug=True +)
\ No newline at end of file diff --git a/vendors/jquery-file-upload/server/gae-python/static/favicon.ico b/vendors/jquery-file-upload/server/gae-python/static/favicon.ico Binary files differnew file mode 100644 index 000000000..1a71ea772 --- /dev/null +++ b/vendors/jquery-file-upload/server/gae-python/static/favicon.ico diff --git a/vendors/jquery-file-upload/server/gae-python/static/robots.txt b/vendors/jquery-file-upload/server/gae-python/static/robots.txt new file mode 100644 index 000000000..eb0536286 --- /dev/null +++ b/vendors/jquery-file-upload/server/gae-python/static/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/vendors/jquery-file-upload/server/node/.gitignore b/vendors/jquery-file-upload/server/node/.gitignore new file mode 100644 index 000000000..9daa8247d --- /dev/null +++ b/vendors/jquery-file-upload/server/node/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +node_modules diff --git a/vendors/jquery-file-upload/server/node/package.json b/vendors/jquery-file-upload/server/node/package.json new file mode 100644 index 000000000..0e0c1aaae --- /dev/null +++ b/vendors/jquery-file-upload/server/node/package.json @@ -0,0 +1,41 @@ +{ + "name": "blueimp-file-upload-node", + "version": "1.0.2", + "title": "jQuery File Upload Node.js example", + "description": "Node.js implementation example of a file upload handler for jQuery File Upload.", + "keywords": [ + "file", + "upload", + "cross-domain", + "cross-site", + "node" + ], + "homepage": "https://github.com/blueimp/jQuery-File-Upload", + "author": { + "name": "Sebastian Tschan", + "url": "https://blueimp.net" + }, + "maintainers": [ + { + "name": "Sebastian Tschan", + "url": "https://blueimp.net" + } + ], + "repository": { + "type": "git", + "url": "git://github.com/blueimp/jQuery-File-Upload.git" + }, + "bugs": "https://github.com/blueimp/jQuery-File-Upload/issues", + "licenses": [ + { + "type": "MIT", + "url": "http://www.opensource.org/licenses/MIT" + } + ], + "dependencies": { + "formidable": ">=1.0.8", + "node-static": ">=0.5.9", + "imagemagick": ">=0.1.2" + }, + "main": "server.js" +} diff --git a/vendors/jquery-file-upload/server/node/public/files/thumbnail/.gitignore b/vendors/jquery-file-upload/server/node/public/files/thumbnail/.gitignore new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/vendors/jquery-file-upload/server/node/public/files/thumbnail/.gitignore diff --git a/vendors/jquery-file-upload/server/node/server.js b/vendors/jquery-file-upload/server/node/server.js new file mode 100755 index 000000000..f1bec542b --- /dev/null +++ b/vendors/jquery-file-upload/server/node/server.js @@ -0,0 +1,285 @@ +#!/usr/bin/env node +/* + * jQuery File Upload Plugin Node.js Example 1.0.2 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2012, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, regexp: true, unparam: true */ +/*global require, __dirname, unescape */ + +(function (port) { + 'use strict'; + var path = require('path'), + fs = require('fs'), + // Since Node 0.8, .existsSync() moved from path to fs: + _existsSync = fs.existsSync || path.existsSync, + formidable = require('formidable'), + nodeStatic = require('node-static'), + imageMagick = require('imagemagick'), + options = { + tmpDir: __dirname + '/tmp', + publicDir: __dirname + '/public', + uploadDir: __dirname + '/public/files', + uploadUrl: '/files/', + maxPostSize: 500000000, // 500 MB + minFileSize: 1, + maxFileSize: 100000000, // 100 MB + acceptFileTypes: /.+/i, + // Files not matched by this regular expression force a download dialog, + // to prevent executing any scripts in the context of the service domain: + safeFileTypes: /\.(gif|jpe?g|png)$/i, + imageTypes: /\.(gif|jpe?g|png)$/i, + imageVersions: { + 'thumbnail': { + width: 80, + height: 80 + } + }, + accessControl: { + allowOrigin: '*', + allowMethods: 'OPTIONS, HEAD, GET, POST, PUT, DELETE' + }, + /* Uncomment and edit this section to provide the service via HTTPS: + ssl: { + key: fs.readFileSync('/Applications/XAMPP/etc/ssl.key/server.key'), + cert: fs.readFileSync('/Applications/XAMPP/etc/ssl.crt/server.crt') + }, + */ + nodeStatic: { + cache: 3600 // seconds to cache served files + } + }, + utf8encode = function (str) { + return unescape(encodeURIComponent(str)); + }, + fileServer = new nodeStatic.Server(options.publicDir, options.nodeStatic), + nameCountRegexp = /(?:(?: \(([\d]+)\))?(\.[^.]+))?$/, + nameCountFunc = function (s, index, ext) { + return ' (' + ((parseInt(index, 10) || 0) + 1) + ')' + (ext || ''); + }, + FileInfo = function (file) { + this.name = file.name; + this.size = file.size; + this.type = file.type; + this.delete_type = 'DELETE'; + }, + UploadHandler = function (req, res, callback) { + this.req = req; + this.res = res; + this.callback = callback; + }, + serve = function (req, res) { + res.setHeader( + 'Access-Control-Allow-Origin', + options.accessControl.allowOrigin + ); + res.setHeader( + 'Access-Control-Allow-Methods', + options.accessControl.allowMethods + ); + var handleResult = function (result, redirect) { + if (redirect) { + res.writeHead(302, { + 'Location': redirect.replace( + /%s/, + encodeURIComponent(JSON.stringify(result)) + ) + }); + res.end(); + } else { + res.writeHead(200, { + 'Content-Type': req.headers.accept + .indexOf('application/json') !== -1 ? + 'application/json' : 'text/plain' + }); + res.end(JSON.stringify(result)); + } + }, + setNoCacheHeaders = function () { + res.setHeader('Pragma', 'no-cache'); + res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate'); + res.setHeader('Content-Disposition', 'inline; filename="files.json"'); + }, + handler = new UploadHandler(req, res, handleResult); + switch (req.method) { + case 'OPTIONS': + res.end(); + break; + case 'HEAD': + case 'GET': + if (req.url === '/') { + setNoCacheHeaders(); + if (req.method === 'GET') { + handler.get(); + } else { + res.end(); + } + } else { + fileServer.serve(req, res); + } + break; + case 'POST': + setNoCacheHeaders(); + handler.post(); + break; + case 'DELETE': + handler.destroy(); + break; + default: + res.statusCode = 405; + res.end(); + } + }; + fileServer.respond = function (pathname, status, _headers, files, stat, req, res, finish) { + if (!options.safeFileTypes.test(files[0])) { + // Force a download dialog for unsafe file extensions: + res.setHeader( + 'Content-Disposition', + 'attachment; filename="' + utf8encode(path.basename(files[0])) + '"' + ); + } else { + // Prevent Internet Explorer from MIME-sniffing the content-type: + res.setHeader('X-Content-Type-Options', 'nosniff'); + } + nodeStatic.Server.prototype.respond + .call(this, pathname, status, _headers, files, stat, req, res, finish); + }; + FileInfo.prototype.validate = function () { + if (options.minFileSize && options.minFileSize > this.size) { + this.error = 'minFileSize'; + } else if (options.maxFileSize && options.maxFileSize < this.size) { + this.error = 'maxFileSize'; + } else if (!options.acceptFileTypes.test(this.name)) { + this.error = 'acceptFileTypes'; + } + return !this.error; + }; + FileInfo.prototype.safeName = function () { + // Prevent directory traversal and creating hidden system files: + this.name = path.basename(this.name).replace(/^\.+/, ''); + // Prevent overwriting existing files: + while (_existsSync(options.uploadDir + '/' + this.name)) { + this.name = this.name.replace(nameCountRegexp, nameCountFunc); + } + }; + FileInfo.prototype.initUrls = function (req) { + if (!this.error) { + var that = this, + baseUrl = (options.ssl ? 'https:' : 'http:') + + '//' + req.headers.host + options.uploadUrl; + this.url = this.delete_url = baseUrl + encodeURIComponent(this.name); + Object.keys(options.imageVersions).forEach(function (version) { + if (_existsSync( + options.uploadDir + '/' + version + '/' + that.name + )) { + that[version + '_url'] = baseUrl + version + '/' + + encodeURIComponent(that.name); + } + }); + } + }; + UploadHandler.prototype.get = function () { + var handler = this, + files = []; + fs.readdir(options.uploadDir, function (err, list) { + list.forEach(function (name) { + var stats = fs.statSync(options.uploadDir + '/' + name), + fileInfo; + if (stats.isFile()) { + fileInfo = new FileInfo({ + name: name, + size: stats.size + }); + fileInfo.initUrls(handler.req); + files.push(fileInfo); + } + }); + handler.callback(files); + }); + }; + UploadHandler.prototype.post = function () { + var handler = this, + form = new formidable.IncomingForm(), + tmpFiles = [], + files = [], + map = {}, + counter = 1, + redirect, + finish = function () { + counter -= 1; + if (!counter) { + files.forEach(function (fileInfo) { + fileInfo.initUrls(handler.req); + }); + handler.callback(files, redirect); + } + }; + form.uploadDir = options.tmpDir; + form.on('fileBegin', function (name, file) { + tmpFiles.push(file.path); + var fileInfo = new FileInfo(file, handler.req, true); + fileInfo.safeName(); + map[path.basename(file.path)] = fileInfo; + files.push(fileInfo); + }).on('field', function (name, value) { + if (name === 'redirect') { + redirect = value; + } + }).on('file', function (name, file) { + var fileInfo = map[path.basename(file.path)]; + fileInfo.size = file.size; + if (!fileInfo.validate()) { + fs.unlink(file.path); + return; + } + fs.renameSync(file.path, options.uploadDir + '/' + fileInfo.name); + if (options.imageTypes.test(fileInfo.name)) { + Object.keys(options.imageVersions).forEach(function (version) { + counter += 1; + var opts = options.imageVersions[version]; + imageMagick.resize({ + width: opts.width, + height: opts.height, + srcPath: options.uploadDir + '/' + fileInfo.name, + dstPath: options.uploadDir + '/' + version + '/' + + fileInfo.name + }, finish); + }); + } + }).on('aborted', function () { + tmpFiles.forEach(function (file) { + fs.unlink(file); + }); + }).on('progress', function (bytesReceived, bytesExpected) { + if (bytesReceived > options.maxPostSize) { + handler.req.connection.destroy(); + } + }).on('end', finish).parse(handler.req); + }; + UploadHandler.prototype.destroy = function () { + var handler = this, + fileName; + if (handler.req.url.slice(0, options.uploadUrl.length) === options.uploadUrl) { + fileName = path.basename(decodeURIComponent(handler.req.url)); + fs.unlink(options.uploadDir + '/' + fileName, function (ex) { + Object.keys(options.imageVersions).forEach(function (version) { + fs.unlink(options.uploadDir + '/' + version + '/' + fileName); + }); + handler.callback(!ex); + }); + } else { + handler.callback(false); + } + }; + if (options.ssl) { + require('https').createServer(options.ssl, serve).listen(port); + } else { + require('http').createServer(serve).listen(port); + } +}(8888)); diff --git a/vendors/jquery-file-upload/server/node/tmp/.gitignore b/vendors/jquery-file-upload/server/node/tmp/.gitignore new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/vendors/jquery-file-upload/server/node/tmp/.gitignore diff --git a/vendors/jquery-file-upload/server/php/files/.htaccess b/vendors/jquery-file-upload/server/php/files/.htaccess new file mode 100644 index 000000000..a6a9f6a75 --- /dev/null +++ b/vendors/jquery-file-upload/server/php/files/.htaccess @@ -0,0 +1,4 @@ +ForceType application/octet-stream +<FilesMatch "(?i)\.(gif|jpe?g|png)$"> + ForceType none +</FilesMatch>
\ No newline at end of file diff --git a/vendors/jquery-file-upload/server/php/index.php b/vendors/jquery-file-upload/server/php/index.php new file mode 100644 index 000000000..1601c76f3 --- /dev/null +++ b/vendors/jquery-file-upload/server/php/index.php @@ -0,0 +1,46 @@ +<?php +/* + * jQuery File Upload Plugin PHP Example 5.7 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +error_reporting(E_ALL | E_STRICT); + +require('upload.class.php'); + +$upload_handler = new UploadHandler(); + +header('Pragma: no-cache'); +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Content-Disposition: inline; filename="files.json"'); +header('X-Content-Type-Options: nosniff'); +header('Access-Control-Allow-Origin: *'); +header('Access-Control-Allow-Methods: OPTIONS, HEAD, GET, POST, PUT, DELETE'); +header('Access-Control-Allow-Headers: X-File-Name, X-File-Type, X-File-Size'); + +switch ($_SERVER['REQUEST_METHOD']) { + case 'OPTIONS': + break; + case 'HEAD': + case 'GET': + $upload_handler->get(); + break; + case 'POST': + if (isset($_REQUEST['_method']) && $_REQUEST['_method'] === 'DELETE') { + $upload_handler->delete(); + } else { + $upload_handler->post(); + } + break; + case 'DELETE': + $upload_handler->delete(); + break; + default: + header('HTTP/1.1 405 Method Not Allowed'); +} diff --git a/vendors/jquery-file-upload/server/php/thumbnails/.htaccess b/vendors/jquery-file-upload/server/php/thumbnails/.htaccess new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/vendors/jquery-file-upload/server/php/thumbnails/.htaccess diff --git a/vendors/jquery-file-upload/server/php/upload.class.php b/vendors/jquery-file-upload/server/php/upload.class.php new file mode 100644 index 000000000..c4efacbdb --- /dev/null +++ b/vendors/jquery-file-upload/server/php/upload.class.php @@ -0,0 +1,436 @@ +<?php +/* + * jQuery File Upload Plugin PHP Class 5.11.2 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +class UploadHandler +{ + protected $options; + + function __construct($options=null) { + $this->options = array( + 'script_url' => $this->getFullUrl().'/', + 'upload_dir' => dirname($_SERVER['SCRIPT_FILENAME']).'/files/', + 'upload_url' => $this->getFullUrl().'/files/', + 'param_name' => 'files', + // Set the following option to 'POST', if your server does not support + // DELETE requests. This is a parameter sent to the client: + 'delete_type' => 'DELETE', + // The php.ini settings upload_max_filesize and post_max_size + // take precedence over the following max_file_size setting: + 'max_file_size' => null, + 'min_file_size' => 1, + 'accept_file_types' => '/.+$/i', + // The maximum number of files for the upload directory: + 'max_number_of_files' => null, + // Image resolution restrictions: + 'max_width' => null, + 'max_height' => null, + 'min_width' => 1, + 'min_height' => 1, + // Set the following option to false to enable resumable uploads: + 'discard_aborted_uploads' => true, + // Set to true to rotate images based on EXIF meta data, if available: + 'orient_image' => false, + 'image_versions' => array( + // Uncomment the following version to restrict the size of + // uploaded images. You can also add additional versions with + // their own upload directories: + /* + 'large' => array( + 'upload_dir' => dirname($_SERVER['SCRIPT_FILENAME']).'/files/', + 'upload_url' => $this->getFullUrl().'/files/', + 'max_width' => 1920, + 'max_height' => 1200, + 'jpeg_quality' => 95 + ), + */ + 'thumbnail' => array( + 'upload_dir' => dirname($_SERVER['SCRIPT_FILENAME']).'/thumbnails/', + 'upload_url' => $this->getFullUrl().'/thumbnails/', + 'max_width' => 80, + 'max_height' => 80 + ) + ) + ); + if ($options) { + $this->options = array_replace_recursive($this->options, $options); + } + } + + protected function getFullUrl() { + $https = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'; + return + ($https ? 'https://' : 'http://'). + (!empty($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'].'@' : ''). + (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ($_SERVER['SERVER_NAME']. + ($https && $_SERVER['SERVER_PORT'] === 443 || + $_SERVER['SERVER_PORT'] === 80 ? '' : ':'.$_SERVER['SERVER_PORT']))). + substr($_SERVER['SCRIPT_NAME'],0, strrpos($_SERVER['SCRIPT_NAME'], '/')); + } + + protected function set_file_delete_url($file) { + $file->delete_url = $this->options['script_url'] + .'?file='.rawurlencode($file->name); + $file->delete_type = $this->options['delete_type']; + if ($file->delete_type !== 'DELETE') { + $file->delete_url .= '&_method=DELETE'; + } + } + + protected function get_file_object($file_name) { + $file_path = $this->options['upload_dir'].$file_name; + if (is_file($file_path) && $file_name[0] !== '.') { + $file = new stdClass(); + $file->name = $file_name; + $file->size = filesize($file_path); + $file->url = $this->options['upload_url'].rawurlencode($file->name); + foreach($this->options['image_versions'] as $version => $options) { + if (is_file($options['upload_dir'].$file_name)) { + $file->{$version.'_url'} = $options['upload_url'] + .rawurlencode($file->name); + } + } + $this->set_file_delete_url($file); + return $file; + } + return null; + } + + protected function get_file_objects() { + return array_values(array_filter(array_map( + array($this, 'get_file_object'), + scandir($this->options['upload_dir']) + ))); + } + + protected function create_scaled_image($file_name, $options) { + $file_path = $this->options['upload_dir'].$file_name; + $new_file_path = $options['upload_dir'].$file_name; + list($img_width, $img_height) = @getimagesize($file_path); + if (!$img_width || !$img_height) { + return false; + } + $scale = min( + $options['max_width'] / $img_width, + $options['max_height'] / $img_height + ); + if ($scale >= 1) { + if ($file_path !== $new_file_path) { + return copy($file_path, $new_file_path); + } + return true; + } + $new_width = $img_width * $scale; + $new_height = $img_height * $scale; + $new_img = @imagecreatetruecolor($new_width, $new_height); + switch (strtolower(substr(strrchr($file_name, '.'), 1))) { + case 'jpg': + case 'jpeg': + $src_img = @imagecreatefromjpeg($file_path); + $write_image = 'imagejpeg'; + $image_quality = isset($options['jpeg_quality']) ? + $options['jpeg_quality'] : 75; + break; + case 'gif': + @imagecolortransparent($new_img, @imagecolorallocate($new_img, 0, 0, 0)); + $src_img = @imagecreatefromgif($file_path); + $write_image = 'imagegif'; + $image_quality = null; + break; + case 'png': + @imagecolortransparent($new_img, @imagecolorallocate($new_img, 0, 0, 0)); + @imagealphablending($new_img, false); + @imagesavealpha($new_img, true); + $src_img = @imagecreatefrompng($file_path); + $write_image = 'imagepng'; + $image_quality = isset($options['png_quality']) ? + $options['png_quality'] : 9; + break; + default: + $src_img = null; + } + $success = $src_img && @imagecopyresampled( + $new_img, + $src_img, + 0, 0, 0, 0, + $new_width, + $new_height, + $img_width, + $img_height + ) && $write_image($new_img, $new_file_path, $image_quality); + // Free up memory (imagedestroy does not delete files): + @imagedestroy($src_img); + @imagedestroy($new_img); + return $success; + } + + protected function validate($uploaded_file, $file, $error, $index) { + if ($error) { + $file->error = $error; + return false; + } + if (!$file->name) { + $file->error = 'missingFileName'; + return false; + } + if (!preg_match($this->options['accept_file_types'], $file->name)) { + $file->error = 'acceptFileTypes'; + return false; + } + if ($uploaded_file && is_uploaded_file($uploaded_file)) { + $file_size = filesize($uploaded_file); + } else { + $file_size = $_SERVER['CONTENT_LENGTH']; + } + if ($this->options['max_file_size'] && ( + $file_size > $this->options['max_file_size'] || + $file->size > $this->options['max_file_size']) + ) { + $file->error = 'maxFileSize'; + return false; + } + if ($this->options['min_file_size'] && + $file_size < $this->options['min_file_size']) { + $file->error = 'minFileSize'; + return false; + } + if (is_int($this->options['max_number_of_files']) && ( + count($this->get_file_objects()) >= $this->options['max_number_of_files']) + ) { + $file->error = 'maxNumberOfFiles'; + return false; + } + list($img_width, $img_height) = @getimagesize($uploaded_file); + if (is_int($img_width)) { + if ($this->options['max_width'] && $img_width > $this->options['max_width'] || + $this->options['max_height'] && $img_height > $this->options['max_height']) { + $file->error = 'maxResolution'; + return false; + } + if ($this->options['min_width'] && $img_width < $this->options['min_width'] || + $this->options['min_height'] && $img_height < $this->options['min_height']) { + $file->error = 'minResolution'; + return false; + } + } + return true; + } + + protected function upcount_name_callback($matches) { + $index = isset($matches[1]) ? intval($matches[1]) + 1 : 1; + $ext = isset($matches[2]) ? $matches[2] : ''; + return ' ('.$index.')'.$ext; + } + + protected function upcount_name($name) { + return preg_replace_callback( + '/(?:(?: \(([\d]+)\))?(\.[^.]+))?$/', + array($this, 'upcount_name_callback'), + $name, + 1 + ); + } + + protected function trim_file_name($name, $type, $index) { + // Remove path information and dots around the filename, to prevent uploading + // into different directories or replacing hidden system files. + // Also remove control characters and spaces (\x00..\x20) around the filename: + $file_name = trim(basename(stripslashes($name)), ".\x00..\x20"); + // Add missing file extension for known image types: + if (strpos($file_name, '.') === false && + preg_match('/^image\/(gif|jpe?g|png)/', $type, $matches)) { + $file_name .= '.'.$matches[1]; + } + if ($this->options['discard_aborted_uploads']) { + while(is_file($this->options['upload_dir'].$file_name)) { + $file_name = $this->upcount_name($file_name); + } + } + return $file_name; + } + + protected function handle_form_data($file, $index) { + // Handle form data, e.g. $_REQUEST['description'][$index] + } + + protected function orient_image($file_path) { + $exif = @exif_read_data($file_path); + if ($exif === false) { + return false; + } + $orientation = intval(@$exif['Orientation']); + if (!in_array($orientation, array(3, 6, 8))) { + return false; + } + $image = @imagecreatefromjpeg($file_path); + switch ($orientation) { + case 3: + $image = @imagerotate($image, 180, 0); + break; + case 6: + $image = @imagerotate($image, 270, 0); + break; + case 8: + $image = @imagerotate($image, 90, 0); + break; + default: + return false; + } + $success = imagejpeg($image, $file_path); + // Free up memory (imagedestroy does not delete files): + @imagedestroy($image); + return $success; + } + + protected function handle_file_upload($uploaded_file, $name, $size, $type, $error, $index = null) { + $file = new stdClass(); + $file->name = $this->trim_file_name($name, $type, $index); + $file->size = intval($size); + $file->type = $type; + if ($this->validate($uploaded_file, $file, $error, $index)) { + $this->handle_form_data($file, $index); + $file_path = $this->options['upload_dir'].$file->name; + $append_file = !$this->options['discard_aborted_uploads'] && + is_file($file_path) && $file->size > filesize($file_path); + clearstatcache(); + if ($uploaded_file && is_uploaded_file($uploaded_file)) { + // multipart/formdata uploads (POST method uploads) + if ($append_file) { + file_put_contents( + $file_path, + fopen($uploaded_file, 'r'), + FILE_APPEND + ); + } else { + move_uploaded_file($uploaded_file, $file_path); + } + } else { + // Non-multipart uploads (PUT method support) + file_put_contents( + $file_path, + fopen('php://input', 'r'), + $append_file ? FILE_APPEND : 0 + ); + } + $file_size = filesize($file_path); + if ($file_size === $file->size) { + if ($this->options['orient_image']) { + $this->orient_image($file_path); + } + $file->url = $this->options['upload_url'].rawurlencode($file->name); + foreach($this->options['image_versions'] as $version => $options) { + if ($this->create_scaled_image($file->name, $options)) { + if ($this->options['upload_dir'] !== $options['upload_dir']) { + $file->{$version.'_url'} = $options['upload_url'] + .rawurlencode($file->name); + } else { + clearstatcache(); + $file_size = filesize($file_path); + } + } + } + } else if ($this->options['discard_aborted_uploads']) { + unlink($file_path); + $file->error = 'abort'; + } + $file->size = $file_size; + $this->set_file_delete_url($file); + } + return $file; + } + + public function get() { + $file_name = isset($_REQUEST['file']) ? + basename(stripslashes($_REQUEST['file'])) : null; + if ($file_name) { + $info = $this->get_file_object($file_name); + } else { + $info = $this->get_file_objects(); + } + header('Content-type: application/json'); + echo json_encode($info); + } + + public function post() { + if (isset($_REQUEST['_method']) && $_REQUEST['_method'] === 'DELETE') { + return $this->delete(); + } + $upload = isset($_FILES[$this->options['param_name']]) ? + $_FILES[$this->options['param_name']] : null; + $info = array(); + if ($upload && is_array($upload['tmp_name'])) { + // param_name is an array identifier like "files[]", + // $_FILES is a multi-dimensional array: + foreach ($upload['tmp_name'] as $index => $value) { + $info[] = $this->handle_file_upload( + $upload['tmp_name'][$index], + isset($_SERVER['HTTP_X_FILE_NAME']) ? + $_SERVER['HTTP_X_FILE_NAME'] : $upload['name'][$index], + isset($_SERVER['HTTP_X_FILE_SIZE']) ? + $_SERVER['HTTP_X_FILE_SIZE'] : $upload['size'][$index], + isset($_SERVER['HTTP_X_FILE_TYPE']) ? + $_SERVER['HTTP_X_FILE_TYPE'] : $upload['type'][$index], + $upload['error'][$index], + $index + ); + } + } elseif ($upload || isset($_SERVER['HTTP_X_FILE_NAME'])) { + // param_name is a single object identifier like "file", + // $_FILES is a one-dimensional array: + $info[] = $this->handle_file_upload( + isset($upload['tmp_name']) ? $upload['tmp_name'] : null, + isset($_SERVER['HTTP_X_FILE_NAME']) ? + $_SERVER['HTTP_X_FILE_NAME'] : (isset($upload['name']) ? + $upload['name'] : null), + isset($_SERVER['HTTP_X_FILE_SIZE']) ? + $_SERVER['HTTP_X_FILE_SIZE'] : (isset($upload['size']) ? + $upload['size'] : null), + isset($_SERVER['HTTP_X_FILE_TYPE']) ? + $_SERVER['HTTP_X_FILE_TYPE'] : (isset($upload['type']) ? + $upload['type'] : null), + isset($upload['error']) ? $upload['error'] : null + ); + } + header('Vary: Accept'); + $json = json_encode($info); + $redirect = isset($_REQUEST['redirect']) ? + stripslashes($_REQUEST['redirect']) : null; + if ($redirect) { + header('Location: '.sprintf($redirect, rawurlencode($json))); + return; + } + if (isset($_SERVER['HTTP_ACCEPT']) && + (strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false)) { + header('Content-type: application/json'); + } else { + header('Content-type: text/plain'); + } + echo $json; + } + + public function delete() { + $file_name = isset($_REQUEST['file']) ? + basename(stripslashes($_REQUEST['file'])) : null; + $file_path = $this->options['upload_dir'].$file_name; + $success = is_file($file_path) && $file_name[0] !== '.' && unlink($file_path); + if ($success) { + foreach($this->options['image_versions'] as $version => $options) { + $file = $options['upload_dir'].$file_name; + if (is_file($file)) { + unlink($file); + } + } + } + header('Content-type: application/json'); + echo json_encode($success); + } + +} diff --git a/vendors/jquery-file-upload/test/index.html b/vendors/jquery-file-upload/test/index.html new file mode 100644 index 000000000..8a8011d21 --- /dev/null +++ b/vendors/jquery-file-upload/test/index.html @@ -0,0 +1,146 @@ +<!DOCTYPE HTML> +<!-- +/* + * jQuery File Upload Plugin Test 6.9.1 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ +--> +<html lang="en"> +<head> +<!-- Force latest IE rendering engine or ChromeFrame if installed --> +<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><![endif]--> +<meta charset="utf-8"> +<title>jQuery File Upload Plugin Test</title> +<meta name="viewport" content="width=device-width"> +<link rel="stylesheet" href="http://code.jquery.com/qunit/git/qunit.css"> +</head> +<body> +<h1 id="qunit-header">jQuery File Upload Plugin Test</h1> +<h2 id="qunit-banner"></h2> +<div id="qunit-testrunner-toolbar"></div> +<h2 id="qunit-userAgent"></h2> +<ol id="qunit-tests"></ol> +<div id="qunit-fixture"> + <!-- The file upload form used as target for the file upload widget --> + <form id="fileupload" action="../server/php/" method="POST" enctype="multipart/form-data"> + <!-- The fileupload-buttonbar contains buttons to add/delete files and start/cancel the upload --> + <div class="row fileupload-buttonbar"> + <div class="span7"> + <!-- The fileinput-button span is used to style the file input field as button --> + <span class="btn btn-success fileinput-button"> + <i class="icon-plus icon-white"></i> + <span>Add files...</span> + <input type="file" name="files[]" multiple> + </span> + <button type="submit" class="btn btn-primary start"> + <i class="icon-upload icon-white"></i> + <span>Start upload</span> + </button> + <button type="reset" class="btn btn-warning cancel"> + <i class="icon-ban-circle icon-white"></i> + <span>Cancel upload</span> + </button> + <button type="button" class="btn btn-danger delete"> + <i class="icon-trash icon-white"></i> + <span>Delete</span> + </button> + <input type="checkbox" class="toggle"> + </div> + <!-- The global progress information --> + <div class="span5 fileupload-progress fade"> + <!-- The global progress bar --> + <div class="progress progress-success progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100"> + <div class="bar" style="width:0%;"></div> + </div> + <!-- The extended global progress information --> + <div class="progress-extended"> </div> + </div> + </div> + <!-- The loading indicator is shown during file processing --> + <div class="fileupload-loading"></div> + <br> + <!-- The table listing the files available for upload/download --> + <table role="presentation" class="table table-striped"><tbody class="files" data-toggle="modal-gallery" data-target="#modal-gallery"></tbody></table> + </form> +</div> +<!-- The template to display files available for upload --> +<script id="template-upload" type="text/x-tmpl"> +{% for (var i=0, file; file=o.files[i]; i++) { %} + <tr class="template-upload"> + <td class="preview"><span class=""></span></td> + <td class="name"><span>{%=file.name%}</span></td> + <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td> + {% if (file.error) { %} + <td class="error" colspan="2"><span class="label label-important">{%=locale.fileupload.error%}</span> {%=locale.fileupload.errors[file.error] || file.error%}</td> + {% } else if (o.files.valid && !i) { %} + <td> + <div class="progress progress-success progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><div class="bar" style="width:0%;"></div></div> + </td> + <td class="start">{% if (!o.options.autoUpload) { %} + <button class="btn btn-primary"> + <i class="icon-upload icon-white"></i> + <span>{%=locale.fileupload.start%}</span> + </button> + {% } %}</td> + {% } else { %} + <td colspan="2"></td> + {% } %} + <td class="cancel">{% if (!i) { %} + <button class="btn btn-warning"> + <i class="icon-ban-circle icon-white"></i> + <span>{%=locale.fileupload.cancel%}</span> + </button> + {% } %}</td> + </tr> +{% } %} +</script> +<!-- The template to display files available for download --> +<script id="template-download" type="text/x-tmpl"> +{% for (var i=0, file; file=o.files[i]; i++) { %} + <tr class="template-download"> + {% if (file.error) { %} + <td></td> + <td class="name"><span>{%=file.name%}</span></td> + <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td> + <td class="error" colspan="2"><span class="label label-important">{%=locale.fileupload.error%}</span> {%=locale.fileupload.errors[file.error] || file.error%}</td> + {% } else { %} + <td class="preview">{% if (file.thumbnail_url) { %} + <a href="{%=file.url%}" title="{%=file.name%}" rel="gallery" download="{%=file.name%}"><img src="{%=file.thumbnail_url%}"></a> + {% } %}</td> + <td class="name"> + <a href="{%=file.url%}" title="{%=file.name%}" rel="{%=file.thumbnail_url&&'gallery'%}" download="{%=file.name%}">{%=file.name%}</a> + </td> + <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td> + <td colspan="2"></td> + {% } %} + <td class="delete"> + <button class="btn btn-danger" data-type="{%=file.delete_type%}" data-url="{%=file.delete_url%}"> + <i class="icon-trash icon-white"></i> + <span>{%=locale.fileupload.destroy%}</span> + </button> + <input type="checkbox" name="delete" value="1"> + </td> + </tr> +{% } %} +</script> +<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> +<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min.js"></script> +<script src="http://blueimp.github.com/JavaScript-Templates/tmpl.min.js"></script> +<script src="http://blueimp.github.com/JavaScript-Load-Image/load-image.min.js"></script> +<script src="http://blueimp.github.com/JavaScript-Canvas-to-Blob/canvas-to-blob.min.js"></script> +<script src="../js/jquery.iframe-transport.js"></script> +<script src="../js/jquery.fileupload.js"></script> +<script src="../js/jquery.fileupload-fp.js"></script> +<script src="../js/jquery.fileupload-ui.js"></script> +<script src="../js/jquery.fileupload-jui.js"></script> +<script src="../js/locale.js"></script> +<script src="http://code.jquery.com/qunit/git/qunit.js"></script> +<script src="test.js"></script> +</body> +</html> diff --git a/vendors/jquery-file-upload/test/test.js b/vendors/jquery-file-upload/test/test.js new file mode 100644 index 000000000..0f6d90afb --- /dev/null +++ b/vendors/jquery-file-upload/test/test.js @@ -0,0 +1,1279 @@ +/* + * jQuery File Upload Plugin Test 6.9.2 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true */ +/*global $, QUnit, document, expect, module, test, asyncTest, start, ok, strictEqual, notStrictEqual */ + +$(function () { + 'use strict'; + + QUnit.done = function () { + // Delete all uploaded files: + var url = $('#fileupload').find('form').prop('action'); + $.getJSON(url, function (files) { + $.each(files, function (index, file) { + $.ajax({ + url: url + '?file=' + encodeURIComponent(file.name), + type: 'DELETE' + }); + }); + }); + }; + + var lifecycle = { + setup: function () { + // Set the .fileupload method to the basic widget method: + $.widget('blueimp.fileupload', $.blueimp.fileupload, {}); + }, + teardown: function () { + // De-initialize the file input plugin: + $('#fileupload:blueimp-fileupload').fileupload('destroy'); + // Remove all remaining event listeners: + $('#fileupload input').unbind(); + $(document).unbind(); + } + }, + lifecycleUI = { + setup: function () { + // Set the .fileupload method to the JUI widget method: + $.widget('blueimpJUI.fileupload', $.blueimpJUI.fileupload, {}); + }, + teardown: function () { + // De-initialize the file input plugin: + $('#fileupload:blueimpJUI-fileupload').fileupload('destroy'); + // Remove all remaining event listeners: + $('#fileupload input, #fileupload button').unbind(); + $(document).unbind(); + } + }; + + module('Initialization', lifecycle); + + test('Widget initialization', function () { + ok($('#fileupload').fileupload().data('fileupload')); + }); + + test('Data attribute options', function () { + $('#fileupload').attr('data-url', 'http://example.org'); + $('#fileupload').fileupload(); + strictEqual( + $('#fileupload').fileupload('option', 'url'), + 'http://example.org' + ); + }); + + test('File input initialization', function () { + var fu = $('#fileupload').fileupload(); + ok( + fu.fileupload('option', 'fileInput').length, + 'File input field inside of the widget' + ); + ok( + fu.fileupload('option', 'fileInput').length, + 'Widget element as file input field' + ); + }); + + test('Drop zone initialization', function () { + ok($('#fileupload').fileupload() + .fileupload('option', 'dropZone').length); + }); + + test('Event listeners initialization', function () { + var fu = $('#fileupload').fileupload(); + ok( + fu.fileupload('option', 'fileInput') + .data('events').change.length, + 'Listens to file input change events' + ); + if ($.support.xhrFormDataFileUpload) { + ok( + fu.fileupload('option', 'dropZone') + .data('events').drop.length, + 'Listens to drop zone drop events' + ); + ok( + fu.fileupload('option', 'dropZone') + .data('events').dragover.length, + 'Listens to drop zone dragover events' + ); + } + }); + + module('API', lifecycle); + + test('destroy', function () { + var fu = $('#fileupload').fileupload(), + fileInput = fu.fileupload('option', 'fileInput'), + dropZone = fu.fileupload('option', 'dropZone'); + fileInput.change($.noop); + dropZone.bind('drop', $.noop); + dropZone.bind('dragover', $.noop); + fu.fileupload('destroy'); + strictEqual( + fileInput.data('events').change.length, + 1, + 'Removes own file input change event listener' + ); + if ($.support.xhrFormDataFileUpload) { + strictEqual( + dropZone.data('events').drop.length, + 1, + 'Removes own drop zone drop event listener' + ); + strictEqual( + dropZone.data('events').dragover.length, + 1, + 'Removes own drop zone dragover event listener' + ); + } + }); + + test('disable', function () { + var fu = $('#fileupload').fileupload(), + fileInput = fu.fileupload('option', 'fileInput'), + dropZone = fu.fileupload('option', 'dropZone'), + param = {files: [{name: 'test'}]}; + fileInput.change($.noop); + dropZone.bind('drop', $.noop); + dropZone.bind('dragover', $.noop); + fu.fileupload('disable'); + strictEqual( + fileInput.data('events').change.length, + 1, + 'Removes own file input change event listener' + ); + if ($.support.xhrFormDataFileUpload) { + strictEqual( + dropZone.data('events').drop.length, + 1, + 'Removes own drop zone drop event listener' + ); + strictEqual( + dropZone.data('events').dragover.length, + 1, + 'Removes own drop zone dragover event listener' + ); + } + fu.fileupload({ + add: function (e, data) { + ok(false); + } + }).fileupload('add', param); + }); + + test('enable', function () { + var fu = $('#fileupload').fileupload(), + param = {files: [{name: 'test'}]}; + fu.fileupload('disable'); + fu.fileupload('enable'); + ok( + fu.fileupload('option', 'fileInput') + .data('events').change.length, + 'Listens to file input change events' + ); + if ($.support.xhrFormDataFileUpload) { + ok( + fu.fileupload('option', 'dropZone') + .data('events').drop.length, + 'Listens to drop zone drop events' + ); + ok( + fu.fileupload('option', 'dropZone') + .data('events').dragover.length, + 'Listens to drop zone dragover events' + ); + } + $('#fileupload').fileupload({ + send: function (e, data) { + strictEqual( + data.files[0].name, + 'test', + 'Triggers send callback' + ); + return false; + } + }).fileupload('send', param); + }); + + test('option', function () { + var fu = $('#fileupload').fileupload(), + fileInput = fu.fileupload('option', 'fileInput'), + dropZone = fu.fileupload('option', 'dropZone'); + fu.fileupload('option', 'fileInput', null); + fu.fileupload('option', 'dropZone', null); + ok( + !fileInput.data('events'), + 'Removes event listener after changing fileInput option' + ); + if ($.support.xhrFormDataFileUpload) { + ok( + !dropZone.data('events'), + 'Removes event listeners after changing dropZone option' + ); + } + fu.fileupload('option', 'fileInput', fileInput); + fu.fileupload('option', 'dropZone', dropZone); + ok( + fileInput.data('events').change.length, + 'Adds change event listener after setting fileInput option' + ); + if ($.support.xhrFormDataFileUpload) { + ok( + dropZone.data('events').drop.length, + 'Adds drop event listener after setting dropZone option' + ); + ok( + dropZone.data('events').dragover.length, + 'Adds dragover event listener after setting dropZone option' + ); + } + fu.fileupload('option', 'dropZone', 'body'); + strictEqual( + fu.fileupload('option', 'dropZone')[0], + document.body, + 'Allow a query string as parameter for the dropZone option' + ); + fu.fileupload('option', 'dropZone', document); + strictEqual( + fu.fileupload('option', 'dropZone')[0], + document, + 'Allow a document element as parameter for the dropZone option' + ); + fu.fileupload('option', 'fileInput', ':file'); + strictEqual( + fu.fileupload('option', 'fileInput')[0], + $(':file')[0], + 'Allow a query string as parameter for the fileInput option' + ); + fu.fileupload('option', 'fileInput', $(':file')[0]); + strictEqual( + fu.fileupload('option', 'fileInput')[0], + $(':file')[0], + 'Allow a document element as parameter for the fileInput option' + ); + }); + + asyncTest('add', function () { + expect(4); + var param = {files: [{name: 'test'}]}, + param2 = {files: [{fileName: 'test', fileSize: 123}]}; + $('#fileupload').fileupload({ + add: function (e, data) { + strictEqual( + data.files[0].name, + param.files[0].name, + 'Triggers add callback' + ); + } + }).fileupload('add', param).fileupload( + 'option', + 'add', + function (e, data) { + strictEqual( + data.files[0].name, + param2.files[0].fileName, + 'Normalizes fileName' + ); + strictEqual( + data.files[0].size, + param2.files[0].fileSize, + 'Normalizes fileSize' + ); + data.submit().complete(function () { + ok(true, 'data.submit() Returns a jqXHR object'); + start(); + }); + } + ).fileupload('add', param2); + }); + + asyncTest('send', function () { + expect(3); + var param = {files: [{name: 'test'}]}; + $('#fileupload').fileupload({ + send: function (e, data) { + strictEqual( + data.files[0].name, + 'test', + 'Triggers send callback' + ); + } + }).fileupload('send', param).fail(function () { + ok(true, 'Allows to abort the request'); + }).complete(function () { + ok(true, 'Returns a jqXHR object'); + start(); + }).abort(); + }); + + module('Callbacks', lifecycle); + + asyncTest('add', function () { + expect(1); + var param = {files: [{name: 'test'}]}; + $('#fileupload').fileupload({ + add: function (e, data) { + ok(true, 'Triggers add callback'); + start(); + } + }).fileupload('add', param); + }); + + asyncTest('submit', function () { + expect(1); + var param = {files: [{name: 'test'}]}; + $('#fileupload').fileupload({ + submit: function (e, data) { + ok(true, 'Triggers submit callback'); + start(); + return false; + } + }).fileupload('add', param); + }); + + asyncTest('send', function () { + expect(1); + var param = {files: [{name: 'test'}]}; + $('#fileupload').fileupload({ + send: function (e, data) { + ok(true, 'Triggers send callback'); + start(); + return false; + } + }).fileupload('send', param); + }); + + asyncTest('done', function () { + expect(1); + var param = {files: [{name: 'test'}]}; + $('#fileupload').fileupload({ + done: function (e, data) { + ok(true, 'Triggers done callback'); + start(); + } + }).fileupload('send', param); + }); + + asyncTest('fail', function () { + expect(1); + var param = {files: [{name: 'test'}]}, + fu = $('#fileupload').fileupload({ + url: '404', + fail: function (e, data) { + ok(true, 'Triggers fail callback'); + start(); + } + }); + fu.data('fileupload')._isXHRUpload = function () { + return true; + }; + fu.fileupload('send', param); + }); + + asyncTest('always', function () { + expect(2); + var param = {files: [{name: 'test'}]}, + counter = 0, + fu = $('#fileupload').fileupload({ + always: function (e, data) { + ok(true, 'Triggers always callback'); + if (counter === 1) { + start(); + } else { + counter += 1; + } + } + }); + fu.data('fileupload')._isXHRUpload = function () { + return true; + }; + fu.fileupload('add', param).fileupload( + 'option', + 'url', + '404' + ).fileupload('add', param); + }); + + asyncTest('progress', function () { + expect(1); + var param = {files: [{name: 'test'}]}, + counter = 0; + $('#fileupload').fileupload({ + forceIframeTransport: true, + progress: function (e, data) { + ok(true, 'Triggers progress callback'); + if (counter === 0) { + start(); + } else { + counter += 1; + } + } + }).fileupload('send', param); + }); + + asyncTest('progressall', function () { + expect(1); + var param = {files: [{name: 'test'}]}, + counter = 0; + $('#fileupload').fileupload({ + forceIframeTransport: true, + progressall: function (e, data) { + ok(true, 'Triggers progressall callback'); + if (counter === 0) { + start(); + } else { + counter += 1; + } + } + }).fileupload('send', param); + }); + + asyncTest('start', function () { + expect(1); + var param = {files: [{name: '1'}, {name: '2'}]}, + active = 0; + $('#fileupload').fileupload({ + send: function (e, data) { + active += 1; + }, + start: function (e, data) { + ok(!active, 'Triggers start callback before uploads'); + start(); + } + }).fileupload('send', param); + }); + + asyncTest('stop', function () { + expect(1); + var param = {files: [{name: '1'}, {name: '2'}]}, + active = 0; + $('#fileupload').fileupload({ + send: function (e, data) { + active += 1; + }, + always: function (e, data) { + active -= 1; + }, + stop: function (e, data) { + ok(!active, 'Triggers stop callback after uploads'); + start(); + } + }).fileupload('send', param); + }); + + test('change', function () { + var fu = $('#fileupload').fileupload(), + fuo = fu.data('fileupload'), + fileInput = fu.fileupload('option', 'fileInput'); + expect(2); + fu.fileupload({ + change: function (e, data) { + ok(true, 'Triggers change callback'); + strictEqual( + data.files.length, + 0, + 'Returns empty files list' + ); + }, + add: $.noop + }); + fuo._onChange({ + data: {fileupload: fuo}, + target: fileInput[0] + }); + }); + + test('paste', function () { + var fu = $('#fileupload').fileupload(), + fuo = fu.data('fileupload'); + expect(1); + fu.fileupload({ + paste: function (e, data) { + ok(true, 'Triggers paste callback'); + }, + add: $.noop + }); + fuo._onPaste({ + data: {fileupload: fuo}, + originalEvent: {clipboardData: {}}, + preventDefault: $.noop + }); + }); + + test('drop', function () { + var fu = $('#fileupload').fileupload(), + fuo = fu.data('fileupload'); + expect(1); + fu.fileupload({ + drop: function (e, data) { + ok(true, 'Triggers drop callback'); + }, + add: $.noop + }); + fuo._onDrop({ + data: {fileupload: fuo}, + originalEvent: {dataTransfer: {}}, + preventDefault: $.noop + }); + }); + + test('dragover', function () { + var fu = $('#fileupload').fileupload(), + fuo = fu.data('fileupload'); + expect(1); + fu.fileupload({ + dragover: function (e, data) { + ok(true, 'Triggers dragover callback'); + }, + add: $.noop + }); + fuo._onDragOver({ + data: {fileupload: fuo}, + originalEvent: {dataTransfer: {}}, + preventDefault: $.noop + }); + }); + + module('Options', lifecycle); + + test('paramName', function () { + expect(1); + var param = {files: [{name: 'test'}]}; + $('#fileupload').fileupload({ + paramName: null, + send: function (e, data) { + strictEqual( + data.paramName[0], + data.fileInput.prop('name'), + 'Takes paramName from file input field if not set' + ); + return false; + } + }).fileupload('send', param); + }); + + test('url', function () { + expect(1); + var param = {files: [{name: 'test'}]}; + $('#fileupload').fileupload({ + url: null, + send: function (e, data) { + strictEqual( + data.url, + $(data.fileInput.prop('form')).prop('action'), + 'Takes url from form action if not set' + ); + return false; + } + }).fileupload('send', param); + }); + + test('type', function () { + expect(2); + var param = {files: [{name: 'test'}]}; + $('#fileupload').fileupload({ + type: null, + send: function (e, data) { + strictEqual( + data.type, + 'POST', + 'Request type is "POST" if not set to "PUT"' + ); + return false; + } + }).fileupload('send', param); + $('#fileupload').fileupload({ + type: 'PUT', + send: function (e, data) { + strictEqual( + data.type, + 'PUT', + 'Request type is "PUT" if set to "PUT"' + ); + return false; + } + }).fileupload('send', param); + }); + + test('replaceFileInput', function () { + var fu = $('#fileupload').fileupload(), + fuo = fu.data('fileupload'), + fileInput = fu.fileupload('option', 'fileInput'), + fileInputElement = fileInput[0]; + expect(2); + fu.fileupload({ + replaceFileInput: false, + change: function (e, data) { + strictEqual( + fu.fileupload('option', 'fileInput')[0], + fileInputElement, + 'Keeps file input with replaceFileInput: false' + ); + }, + add: $.noop + }); + fuo._onChange({ + data: {fileupload: fuo}, + target: fileInput[0] + }); + fu.fileupload({ + replaceFileInput: true, + change: function (e, data) { + notStrictEqual( + fu.fileupload('option', 'fileInput')[0], + fileInputElement, + 'Replaces file input with replaceFileInput: true' + ); + }, + add: $.noop + }); + fuo._onChange({ + data: {fileupload: fuo}, + target: fileInput[0] + }); + }); + + asyncTest('forceIframeTransport', function () { + expect(1); + var param = {files: [{name: 'test'}]}; + $('#fileupload').fileupload({ + forceIframeTransport: true, + done: function (e, data) { + strictEqual( + data.dataType.substr(0, 6), + 'iframe', + 'Iframe Transport is used' + ); + start(); + } + }).fileupload('send', param); + }); + + test('singleFileUploads', function () { + expect(3); + var fu = $('#fileupload').fileupload(), + param = {files: [{name: '1'}, {name: '2'}]}, + index = 1; + fu.data('fileupload')._isXHRUpload = function () { + return true; + }; + $('#fileupload').fileupload({ + singleFileUploads: true, + add: function (e, data) { + ok(true, 'Triggers callback number ' + index.toString()); + index += 1; + } + }).fileupload('add', param).fileupload( + 'option', + 'singleFileUploads', + false + ).fileupload('add', param); + }); + + test('limitMultiFileUploads', function () { + expect(3); + var fu = $('#fileupload').fileupload(), + param = {files: [ + {name: '1'}, + {name: '2'}, + {name: '3'}, + {name: '4'}, + {name: '5'} + ]}, + index = 1; + fu.data('fileupload')._isXHRUpload = function () { + return true; + }; + $('#fileupload').fileupload({ + singleFileUploads: false, + limitMultiFileUploads: 2, + add: function (e, data) { + ok(true, 'Triggers callback number ' + index.toString()); + index += 1; + } + }).fileupload('add', param); + }); + + asyncTest('sequentialUploads', function () { + expect(6); + var param = {files: [ + {name: '1'}, + {name: '2'}, + {name: '3'}, + {name: '4'}, + {name: '5'}, + {name: '6'} + ]}, + addIndex = 0, + sendIndex = 0, + loadIndex = 0, + fu = $('#fileupload').fileupload({ + sequentialUploads: true, + add: function (e, data) { + addIndex += 1; + if (addIndex === 4) { + data.submit().abort(); + } else { + data.submit(); + } + }, + send: function (e, data) { + sendIndex += 1; + }, + done: function (e, data) { + loadIndex += 1; + strictEqual(sendIndex, loadIndex, 'upload in order'); + }, + fail: function (e, data) { + strictEqual(data.errorThrown, 'abort', 'upload aborted'); + }, + stop: function (e) { + start(); + } + }); + fu.data('fileupload')._isXHRUpload = function () { + return true; + }; + fu.fileupload('add', param); + }); + + asyncTest('limitConcurrentUploads', function () { + expect(12); + var param = {files: [ + {name: '1'}, + {name: '2'}, + {name: '3'}, + {name: '4'}, + {name: '5'}, + {name: '6'}, + {name: '7'}, + {name: '8'}, + {name: '9'}, + {name: '10'}, + {name: '11'}, + {name: '12'} + ]}, + addIndex = 0, + sendIndex = 0, + loadIndex = 0, + fu = $('#fileupload').fileupload({ + limitConcurrentUploads: 3, + add: function (e, data) { + addIndex += 1; + if (addIndex === 4) { + data.submit().abort(); + } else { + data.submit(); + } + }, + send: function (e, data) { + sendIndex += 1; + }, + done: function (e, data) { + loadIndex += 1; + ok(sendIndex - loadIndex < 3); + }, + fail: function (e, data) { + strictEqual(data.errorThrown, 'abort', 'upload aborted'); + }, + stop: function (e) { + start(); + } + }); + fu.data('fileupload')._isXHRUpload = function () { + return true; + }; + fu.fileupload('add', param); + }); + + if ($.support.xhrFileUpload) { + asyncTest('multipart', function () { + expect(4); + var param = {files: [{ + name: 'test.png', + size: 123, + type: 'image/png' + }]}, + fu = $('#fileupload').fileupload({ + multipart: false, + always: function (e, data) { + strictEqual( + data.contentType, + param.files[0].type, + 'non-multipart upload sets file type as contentType' + ); + strictEqual( + data.headers['X-File-Name'], + param.files[0].name, + 'non-multipart upload sets X-File-Name header' + ); + strictEqual( + data.headers['X-File-Type'], + param.files[0].type, + 'non-multipart upload sets X-File-Type header' + ); + strictEqual( + data.headers['X-File-Size'], + param.files[0].size, + 'non-multipart upload sets X-File-Size header' + ); + start(); + } + }); + fu.fileupload('send', param); + }); + } + + module('UI Initialization', lifecycleUI); + + test('Widget initialization', function () { + ok($('#fileupload').fileupload().data('fileupload')); + ok( + $('#fileupload').fileupload('option', 'uploadTemplate').length, + 'Initialized upload template' + ); + ok( + $('#fileupload').fileupload('option', 'downloadTemplate').length, + 'Initialized download template' + ); + }); + + test('Buttonbar event listeners', function () { + var buttonbar = $('#fileupload .fileupload-buttonbar'), + files = [{name: 'test'}]; + expect(7); + $('#fileupload').fileupload({ + send: function (e, data) { + ok(true, 'Started file upload via global start button'); + }, + fail: function (e, data) { + ok(true, 'Canceled file upload via global cancel button'); + data.context.remove(); + }, + destroy: function (e, data) { + ok(true, 'Delete action called via global delete button'); + } + }); + ok( + buttonbar.find('.start') + .data('events').click.length, + 'Listens to start button click events' + ); + ok( + buttonbar.find('.cancel') + .data('events').click.length, + 'Listens to cancel button click events' + ); + ok( + buttonbar.find('.delete') + .data('events').click.length, + 'Listens to delete button click events' + ); + $('#fileupload').fileupload('add', {files: files}); + buttonbar.find('.cancel').click(); + $('#fileupload').fileupload('add', {files: files}); + buttonbar.find('.start').click(); + buttonbar.find('.cancel').click(); + $('#fileupload').data('fileupload')._renderDownload(files) + .appendTo($('#fileupload .files')).show() + .find('.delete input').click(); + buttonbar.find('.delete').click(); + }); + + module('UI API', lifecycleUI); + + test('destroy', function () { + var buttonbar = $('#fileupload .fileupload-buttonbar'); + $('#fileupload').fileupload(); + buttonbar.find('button').click($.noop); + $('#fileupload').fileupload('destroy'); + strictEqual( + buttonbar.find('.start').data('events').click.length, + 1, + 'Removes own start button click event listener' + ); + strictEqual( + buttonbar.find('.cancel').data('events').click.length, + 1, + 'Removes own cancel button click event listener' + ); + strictEqual( + buttonbar.find('.delete').data('events').click.length, + 1, + 'Removes own delete button click event listener' + ); + }); + + test('disable', function () { + var buttonbar = $('#fileupload .fileupload-buttonbar'); + $('#fileupload').fileupload(); + $('#fileupload').fileupload('disable'); + strictEqual( + buttonbar.find('input[type=file], button').not(':disabled').length, + 0, + 'Disables the buttonbar buttons' + ); + }); + + test('enable', function () { + var buttonbar = $('#fileupload .fileupload-buttonbar'); + $('#fileupload') + .fileupload() + .fileupload('disable') + .fileupload('enable'); + strictEqual( + buttonbar.find('input[type=file], button').not(':disabled').length, + 4, + 'Enables the buttonbar buttons' + ); + }); + + module('UI Callbacks', lifecycleUI); + + test('destroy', function () { + expect(3); + $('#fileupload').fileupload({ + destroy: function (e, data) { + ok(true, 'Triggers destroy callback'); + strictEqual( + data.url, + 'test', + 'Passes over deletion url parameter' + ); + strictEqual( + data.type, + 'DELETE', + 'Passes over deletion request type parameter' + ); + } + }); + $('#fileupload').data('fileupload')._renderDownload([{ + name: 'test', + delete_url: 'test', + delete_type: 'DELETE' + }]).appendTo($('#fileupload .files')).show() + .find('.delete input').click(); + $('#fileupload .fileupload-buttonbar .delete').click(); + }); + + asyncTest('added', function () { + expect(1); + var param = {files: [{name: 'test'}]}; + $('#fileupload').fileupload({ + added: function (e, data) { + start(); + strictEqual( + data.files[0].name, + param.files[0].name, + 'Triggers added callback' + ); + }, + send: function () { + return false; + } + }).fileupload('add', param); + }); + + asyncTest('started', function () { + expect(1); + var param = {files: [{name: 'test'}]}; + $('#fileupload').fileupload({ + started: function (e) { + start(); + ok('Triggers started callback'); + return false; + }, + sent: function (e, data) { + return false; + } + }).fileupload('send', param); + }); + + asyncTest('sent', function () { + expect(1); + var param = {files: [{name: 'test'}]}; + $('#fileupload').fileupload({ + sent: function (e, data) { + start(); + strictEqual( + data.files[0].name, + param.files[0].name, + 'Triggers sent callback' + ); + return false; + } + }).fileupload('send', param); + }); + + asyncTest('completed', function () { + expect(1); + var param = {files: [{name: 'test'}]}; + $('#fileupload').fileupload({ + completed: function (e, data) { + start(); + ok('Triggers completed callback'); + return false; + } + }).fileupload('send', param); + }); + + asyncTest('failed', function () { + expect(1); + var param = {files: [{name: 'test'}]}; + $('#fileupload').fileupload({ + failed: function (e, data) { + start(); + ok('Triggers failed callback'); + return false; + } + }).fileupload('send', param).abort(); + }); + + asyncTest('stopped', function () { + expect(1); + var param = {files: [{name: 'test'}]}; + $('#fileupload').fileupload({ + stopped: function (e, data) { + start(); + ok('Triggers stopped callback'); + return false; + } + }).fileupload('send', param); + }); + + asyncTest('destroyed', function () { + expect(1); + $('#fileupload').fileupload({ + destroyed: function (e, data) { + start(); + ok(true, 'Triggers destroyed callback'); + } + }); + $('#fileupload').data('fileupload')._renderDownload([{ + name: 'test', + delete_url: 'test', + delete_type: 'DELETE' + }]).appendTo($('#fileupload .files')).show() + .find('.delete input').click(); + $('#fileupload .fileupload-buttonbar .delete').click(); + }); + + module('UI Options', lifecycleUI); + + test('autoUpload', function () { + expect(1); + $('#fileupload') + .fileupload({ + autoUpload: true, + send: function (e, data) { + ok(true, 'Started file upload automatically'); + return false; + } + }) + .fileupload('add', {files: [{name: 'test'}]}) + .fileupload('option', 'autoUpload', false) + .fileupload('add', {files: [{name: 'test'}]}); + }); + + test('maxNumberOfFiles', function () { + expect(4); + var addIndex = 0, + sendIndex = 0; + $('#fileupload') + .fileupload({ + autoUpload: true, + maxNumberOfFiles: 1, + singleFileUploads: false, + send: function (e, data) { + strictEqual( + sendIndex += 1, + addIndex + ); + }, + progress: $.noop, + progressall: $.noop, + done: $.noop, + stop: $.noop + }) + .fileupload('add', {files: [{name: (addIndex += 1)}]}) + .fileupload('add', {files: [{name: 'test'}]}) + .fileupload('option', 'maxNumberOfFiles', 1) + .fileupload('add', {files: [{name: 1}, {name: 2}]}) + .fileupload({ + maxNumberOfFiles: 1, + send: function (e, data) { + strictEqual( + sendIndex += 1, + addIndex + ); + return false; + } + }) + .fileupload('add', {files: [{name: (addIndex += 1)}]}) + .fileupload('add', {files: [{name: (addIndex += 1)}]}) + .fileupload({ + maxNumberOfFiles: 0, + send: function (e, data) { + ok( + !$.blueimpUI.fileupload.prototype.options + .send.call(this, e, data) + ); + return false; + } + }) + .fileupload('send', {files: [{name: 'test'}]}); + }); + + test('maxFileSize', function () { + expect(3); + var addIndex = 0, + sendIndex = 0; + $('#fileupload') + .fileupload({ + autoUpload: true, + maxFileSize: 1000, + send: function (e, data) { + strictEqual( + sendIndex += 1, + addIndex + ); + return false; + } + }) + .fileupload('add', {files: [{ + name: (addIndex += 1) + }]}) + .fileupload('add', {files: [{ + name: (addIndex += 1), + size: 999 + }]}) + .fileupload('add', {files: [{ + name: 'test', + size: 1001 + }]}) + .fileupload({ + send: function (e, data) { + ok( + !$.blueimpUI.fileupload.prototype.options + .send.call(this, e, data) + ); + return false; + } + }) + .fileupload('send', {files: [{ + name: 'test', + size: 1001 + }]}); + }); + + test('minFileSize', function () { + expect(3); + var addIndex = 0, + sendIndex = 0; + $('#fileupload') + .fileupload({ + autoUpload: true, + minFileSize: 1000, + send: function (e, data) { + strictEqual( + sendIndex += 1, + addIndex + ); + return false; + } + }) + .fileupload('add', {files: [{ + name: (addIndex += 1) + }]}) + .fileupload('add', {files: [{ + name: (addIndex += 1), + size: 1001 + }]}) + .fileupload('add', {files: [{ + name: 'test', + size: 999 + }]}) + .fileupload({ + send: function (e, data) { + ok( + !$.blueimpUI.fileupload.prototype.options + .send.call(this, e, data) + ); + return false; + } + }) + .fileupload('send', {files: [{ + name: 'test', + size: 999 + }]}); + }); + + test('acceptFileTypes', function () { + expect(3); + var addIndex = 0, + sendIndex = 0; + $('#fileupload') + .fileupload({ + autoUpload: true, + acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, + previewFileTypes: /none/, + send: function (e, data) { + strictEqual( + sendIndex += 1, + addIndex + ); + return false; + } + }) + .fileupload('add', {files: [{ + name: (addIndex += 1) + '.jpg' + }]}) + .fileupload('add', {files: [{ + name: (addIndex += 1), + type: 'image/jpeg' + }]}) + .fileupload('add', {files: [{ + name: 'test.txt', + type: 'text/plain' + }]}) + .fileupload({ + send: function (e, data) { + ok( + !$.blueimpUI.fileupload.prototype.options + .send.call(this, e, data) + ); + return false; + } + }) + .fileupload('send', {files: [{ + name: 'test.txt', + type: 'text/plain' + }]}); + }); + + test('acceptFileTypes as HTML5 data attribute', function () { + expect(2); + var regExp = /(\.|\/)(gif|jpe?g|png)$/i; + $('#fileupload') + .attr('data-accept-file-types', regExp.toString()) + .fileupload(); + strictEqual( + $.type($('#fileupload').fileupload('option', 'acceptFileTypes')), + $.type(regExp) + ); + strictEqual( + $('#fileupload').fileupload('option', 'acceptFileTypes').toString(), + regExp.toString() + ); + }); + +}); diff --git a/views/default/forms/photos/basic_upload.php b/views/default/forms/photos/basic_upload.php index ba849d1b4..9e71cf59e 100644 --- a/views/default/forms/photos/basic_upload.php +++ b/views/default/forms/photos/basic_upload.php @@ -1,48 +1,63 @@ <?php -/** - * Basic uploader form - * - * This only handled uploading the images. Editing the titles and descriptions - * are handled with the edit forms. - * - * @uses $vars['entity'] - * - * @author Cash Costello - * @license http://www.gnu.org/licenses/gpl-2.0.html GNU General Public License v2 - */ - $album = $vars['entity']; -$access_id = $album->access_id; +$help = elgg_echo('tidypics:uploader:help'); -$maxfilesize = (float) elgg_get_plugin_setting('maxfilesize', 'tidypics'); +$input = elgg_view('input/file', array( + 'name' => 'images[]', + 'multiple' => 'multiple', + 'class' => 'hidden-js', +)); -$instructions = elgg_echo("tidypics:uploader:upload"); -$max = elgg_echo('tidypics:uploader:basic', array($maxfilesize)); +$button = elgg_view('output/url', array( + 'text' => elgg_echo('tidypics:uploader:upload') . $input, + 'class' => 'elgg-button elgg-button-action fileinput-button', +)); -$list = ''; -for ($x = 0; $x < 10; $x++) { - $list .= '<li>' . elgg_view('input/file', array('name' => 'images[]')) . '</li>'; -} +$reset = elgg_view('input/reset', array( + 'value' => elgg_echo('cancel'), + 'class' => 'hidden', +)); $foot = elgg_view('input/hidden', array('name' => 'guid', 'value' => $album->getGUID())); $foot .= elgg_view('input/submit', array('value' => elgg_echo("save"))); -$form_body = <<<HTML +echo <<<HTML <div> $max </div> -<div> - <ol> - $list - </ol> +<div class="fileinput-container"> + $button + $reset + <p class="elgg-text-help">$help</p> +</div> +<div class="mtm"><!-- The table listing the files available for upload/download --> + <table role="presentation" class="elgg-table-alt clearfloat mtm"> + <tbody class="files"></tbody> + </table> </div> <div class='elgg-foot'> $foot </div> HTML; -echo elgg_view('input/form', array( - 'body' => $form_body, - 'action' => 'action/photos/image/upload', - 'enctype' => 'multipart/form-data', -)); +?> + +<noscript><style type="text/css">hidden-nojs {display: hidden}</style></noscript> + +<!-- The template to display files available for upload --> +<script id="template-upload" type="text/x-tmpl"> +{% for (var i=0, file; file=o.files[i]; i++) { %} + <tr class="template-upload fade"> + {% if (file.error) { %} + <td class="error"><span class="elgg-message elgg-state-error">{%=locale.fileupload.error%} {%=locale.fileupload.errors[file.error] || file.error%}</span></td> + {% } else { %} + <td class="preview"><span class="fade"></span></td> + {% } %} + <td class="name"><span>{%=file.name%}</span></td> + <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td> + + </tr> +{% } %} +</script> +<!-- The template to display files available for download --> +<script id="template-download" type="text/x-tmpl" /> diff --git a/views/default/photos/css.php b/views/default/photos/css.php index 49a7e139a..8fd64fd0f 100644 --- a/views/default/photos/css.php +++ b/views/default/photos/css.php @@ -72,6 +72,68 @@ vertical-align: top; } +/* *************************************** + UPLOADER +*************************************** */ + +.fileinput-container { + text-align: center; +} +.fileinput-button { + cursor: pointer; +} +.fileinput-button input { + position: absolute; + top: 0; + right: 0; + margin: 0; + opacity: 0; + filter: alpha(opacity=0); + +} +.files .fade { + display: none; +} +/* Fix for IE 6: */ +*html .fileinput-button { + margin-right: -2px; +} +*html .fileinput-button .elgg-button { + line-height: 24px; +} +*html .fileupload-buttonbar .elgg-button { + margin-left: 3px; +} + +/* Fix for IE 7: */ +*+html .fileinput-button { + margin-right: 1px; +} +*+html .fileinput-button .elgg-button { + line-height: 24px; +} +*+html .fileupload-buttonbar .elgg-button { + margin-left: 3px; +} + +@media (max-width: 480px) { + .files .preview * { + width: 40px; + } + .files .name * { + width: 80px; + display: inline-block; + word-wrap: break-word; + } +} + +/* Fix for Webkit (Safari, Chrome) */ +@media screen and (-webkit-min-device-pixel-ratio:0) { + .fileinput-button { + margin-top: 2px; + } +} + <?php return true; ?> |