Skip to content
Snippets Groups Projects
Commit 232d85eb authored by Jared Hancock's avatar Jared Hancock
Browse files

forms: Add file upload field type

parent 4d861025
No related branches found
No related tags found
No related merge requests found
.filedrop .dropzone {
border: 2px dashed rgba(0, 0, 0, 0.2);
padding: 8px;
border-radius: 5px;
background-color: rgba(0, 0, 0, 0.05);
color: #999;
}
.filedrop .dropzone a {
color: rgba(24, 78, 129, 0.7);
}
.filedrop .files:not(:empty) {
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 5px 5px 0 0;
padding: 5px;
}
.filedrop .files:not(:empty) + .dropzone {
border-top: none;
border-radius: 0 0 5px 5px;
}
.filedrop .files .file {
display: block;
padding: 5px 10px 5px 20px;
margin: 0;
border-radius: 5px;
}
.filedrop .files .file:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.filedrop .files .file .filesize {
margin-left: 1em;
color: #999;
}
.filedrop .files .file .upload-rate {
margin-right: 10px;
color: #aaa;
}
.filedrop .files .file .trash {
cursor: pointer;
}
/* Bootstrap 3.2 progress-bar */
@-webkit-keyframes progress-bar-stripes {
from {
background-position: 40px 0;
}
to {
background-position: 0 0;
}
}
@-o-keyframes progress-bar-stripes {
from {
background-position: 40px 0;
}
to {
background-position: 0 0;
}
}
@keyframes progress-bar-stripes {
from {
background-position: 40px 0;
}
to {
background-position: 0 0;
}
}
.progress {
height: 10px;
overflow: hidden;
background-color: #f5f5f5;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
}
.progress-bar {
float: left;
width: 0;
height: 100%;
font-size: 12px;
line-height: 20px;
color: #fff;
text-align: center;
background-color: #428bca;
-webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
-webkit-transition: width .6s ease;
-o-transition: width .6s ease;
transition: width .6s ease;
}
.progress-striped .progress-bar,
.progress-bar-striped {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
-webkit-background-size: 40px 40px;
background-size: 40px 40px;
}
.progress.active .progress-bar,
.progress-bar.active {
-webkit-animation: progress-bar-stripes 2s linear infinite;
-o-animation: progress-bar-stripes 2s linear infinite;
animation: progress-bar-stripes 2s linear infinite;
}
.progress-bar[aria-valuenow="1"],
.progress-bar[aria-valuenow="2"] {
min-width: 30px;
}
.progress-bar[aria-valuenow="0"] {
min-width: 30px;
color: #777;
background-color: transparent;
background-image: none;
-webkit-box-shadow: none;
box-shadow: none;
}
.progress-bar-success {
background-color: #5cb85c;
}
.progress-striped .progress-bar-success {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
}
.progress-bar-info {
background-color: #5bc0de;
}
.progress-striped .progress-bar-info {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
}
.progress-bar-warning {
background-color: #f0ad4e;
}
.progress-striped .progress-bar-warning {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
}
.progress-bar-danger {
background-color: #d9534f;
}
.progress-striped .progress-bar-danger {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
}
......@@ -2,6 +2,7 @@
require_once(INCLUDE_DIR . 'class.topic.php');
require_once(INCLUDE_DIR . 'class.dynamic_forms.php');
require_once(INCLUDE_DIR . 'class.forms.php');
class DynamicFormsAjaxAPI extends AjaxController {
function getForm($form_id) {
......@@ -81,5 +82,22 @@ class DynamicFormsAjaxAPI extends AjaxController {
Http::response(201, 'Successfully updated record');
}
function upload($id) {
if (!$field = DynamicFormField::lookup($id))
Http::response(400, 'No such field');
$impl = $field->getImpl();
if (!$impl instanceof FileUploadField)
Http::response(400, 'Upload to a non file-field');
return $impl->upload();
}
function attach() {
$field = new FileUploadField();
$field->loadSystemDefaultConfig();
return $field->upload();
}
}
?>
......@@ -168,6 +168,7 @@ class FormField {
'phone' => array( /* @trans */ 'Phone Number', 'PhoneField'),
'bool' => array( /* @trans */ 'Checkbox', 'BooleanField'),
'choices' => array( /* @trans */ 'Choices', 'ChoiceField'),
'files' => array( /* @trans */ 'File Upload', 'FileUploadField'),
'break' => array( /* @trans */ 'Section Break', 'SectionBreakField'),
),
);
......@@ -1206,6 +1207,145 @@ FormField::addFieldTypes('Dynamic Fields', function() {
);
});
class FileUploadField extends FormField {
static $widget = 'FileUploadWidget';
protected $attachments;
function getConfigurationOptions() {
// Compute size selections
$sizes = array('262144' => '— Small —');
$next = 512 << 10;
$max = strtoupper(ini_get('upload_max_filesize'));
$limit = (int) $max;
if (!$limit) $limit = 2 << 20; # 2M default value
elseif (strpos($max, 'K')) $limit <<= 10;
elseif (strpos($max, 'M')) $limit <<= 20;
elseif (strpos($max, 'G')) $limit <<= 30;
while ($next <= $limit) {
// Select the closest, larger value (in case the
// current value is between two)
$diff = $next - $config['max_file_size'];
$sizes[$next] = Format::file_size($next);
$next *= 2;
}
// Add extra option if top-limit in php.ini doesn't fall
// at a power of two
if ($next < $limit * 2)
$sizes[$limit] = Format::file_size($limit);
return array(
'size' => new ChoiceField(array(
'label'=>'Maximum File Size',
'hint'=>'Maximum size of a single file uploaded to this field',
'default'=>'262144',
'choices'=>$sizes
)),
'extensions' => new TextareaField(array(
'label'=>'Allowed Extensions',
'hint'=>'Enter allowed file extensions separated by a comma.
e.g .doc, .pdf. To accept all files enter wildcard
<b><i>.*</i></b> — i.e dotStar (NOT Recommended).',
'default'=>'.doc, .pdf, .jpg, .jpeg, .gif, .png, .xls, .docx, .xlsx, .txt',
'configuration'=>array('html'=>false, 'rows'=>2),
)),
'max' => new TextboxField(array(
'label'=>'Maximum Files',
'hint'=>'Users cannot upload more than this many files',
'default'=>false,
'required'=>false,
'validator'=>'number',
'configuration'=>array('size'=>4, 'length'=>4),
))
);
}
function loadSystemDefaultConfig() {
global $cfg;
$this->_config = array(
'max' => $cfg->getStaffMaxFileUploads(),
'size' => $cfg->getMaxFileSize(),
'extensions' => $cfg->getAllowedFileTypes(),
);
}
function upload() {
$config = $this->getConfiguration();
$files = AttachmentFile::format($_FILES['upload'],
// For numeric fields assume configuration exists
is_numeric($this->get('id')));
if (count($files) != 1)
Http::response(400, 'Send one file at a time');
$file = array_shift($files);
$file['name'] = urldecode($file['name']);
// TODO: Check allowed type / size.
// Return HTTP/413, 415, 417 or similar
if (!($id = AttachmentFile::upload($file)))
Http::response(500, 'Unable to store file');
Http::response(200, $id);
}
function getFiles() {
if (!isset($this->attachments) && ($a = $this->getAnswer())
&& ($e = $a->getEntry()) && ($e->get('id'))
) {
$this->attachments = new GenericAttachments(
// Combine the field and entry ids to make the key
sprintf('%u', crc32('E'.$this->get('id').$e->get('id'))),
'E');
}
return $this->attachments ? $this->attachments->getAll() : array();
}
// When the field is saved to database, encode the ID listing as a json
// array. Then, inspect the difference between the files actually
// attached to this field
function to_database($value) {
$this->getFiles();
if (isset($this->attachments)) {
$ids = array();
// Handle deletes
foreach ($this->attachments->getAll() as $f) {
if (!in_array($f['id'], $value))
$this->attachments->delete($f['id']);
else
$ids[] = $f['id'];
}
// Handle new files
foreach ($value as $id) {
if (!in_array($id, $ids))
$this->attachments->upload($id);
}
}
return JsonDataEncoder::encode($value);
}
function parse($value) {
// Values in the database should be integer file-ids
return array_map(function($e) { return (int) $e; },
$value ?: array());
}
function to_php($value) {
return JsonDataParser::decode($value);
}
function display($value) {
$links = array();
foreach ($this->getFiles() as $f) {
$hash = strtolower($f['key']
. md5($f['id'].session_id().strtolower($f['key'])));
$links[] = sprintf('<a class="no-pjax" href="file.php?h=%s">%s</a>',
$hash, Format::htmlchars($f['name']));
}
return implode('<br/>', $links);
}
}
class Widget {
static $media = null;
......@@ -1568,4 +1708,69 @@ class ThreadEntryWidget extends Widget {
}
}
class FileUploadWidget extends Widget {
static $media = array(
'js' => array(
'/js/filedrop.field.js'
),
'css' => array(
'/css/filedrop.css',
),
);
function render($how) {
$config = $this->field->getConfiguration();
$name = $this->field->getFormName();
$attachments = $this->field->getFiles();
$files = array();
foreach ($this->value ?: array() as $id) {
$found = false;
foreach ($attachments as $f) {
if ($f['id'] == $id) {
$files[] = $f;
$found = true;
break;
}
}
if (!$found && ($file = AttachmentFile::lookup($id))) {
$files[] = array(
'id' => $file->getId(),
'name' => $file->getName(),
'type' => $file->getType(),
'size' => $file->getSize(),
);
}
}
?><div id="filedrop-<?php echo $name;
?>" class="filedrop"><div class="files"></div>
<div class="dropzone"><i class="icon-upload"></i>
Drop files here or <a href="#" class="manual">choose
them</a></div></div>
<input type="file" id="file-<?php echo $name; ?>" style="display:none;"/>
<script type="text/javascript">
$(function(){$('#filedrop-<?php echo $name; ?> .dropzone').filedropbox({
url: 'ajax.php/form/upload/<?php echo $this->field->get('id') ?>',
link: $('#filedrop-<?php echo $name; ?>').find('a.manual'),
paramname: 'upload[]',
fallback_id: 'file-<?php echo $name; ?>',
allowedfileextensions: '<?php echo $config['extensions'];
?>'.split(/,\s*/),
maxfiles: <?php echo $config['max'] ?: 20; ?>,
maxfilesize: <?php echo $config['filesize'] ?: 1048576 / 1048576; ?>,
name: '<?php echo $name; ?>[]',
files: <?php echo JsonDataEncoder::encode($files); ?>
});});
</script>
<?php
}
function getValue() {
$data = $this->field->getSource();
// If no value was sent, assume an empty list
if ($data && is_array($data) && !isset($data[$this->name]))
return array();
return parent::getValue();
}
}
?>
!function($) {
"use strict";
var FileDropbox = function(element, options) {
this.$element = $(element);
this.uploads = [];
var events = {
uploadStarted: $.proxy(this.uploadStarted, this),
uploadFinished: $.proxy(this.uploadFinished, this),
progressUpdated: $.proxy(this.progressUpdated, this),
speedUpdated: $.proxy(this.speedUpdated, this),
dragOver: $.proxy(this.dragOver, this),
drop: $.proxy(this.dragLeave, this),
error: $.proxy(this.handleError, this)
};
this.options = $.extend({}, $.fn.filedropbox.defaults, events, options);
this.$element.filedrop(this.options);
(this.options.files || []).forEach($.proxy(this.addNode, this));
};
FileDropbox.prototype = {
drop: function(e) {
this.$element.removeAttr('style');
},
dragOver: function(box, e) {
this.$element.css('background-color', 'rgba(0, 0, 0, 0.3)');
},
speedUpdated: function(i, file, speed) {
var that = this;
this.uploads.some(function(e) {
if (e.data('file') == file) {
e.find('.upload-rate').text(that.fileSize(speed * 1024)+'/s');
return true;
}
});
},
progressUpdated: function(i, file, value) {
this.uploads.some(function(e) {
if (e.data('file') == file) {
e.find('.progress').show();
e.find('.progress-bar')
.width(value + '%')
.attr({'aria-valuenow': value})
if (value > 99)
e.find('.progress-bar').addClass('progress-bar-striped active')
return true;
}
});
},
uploadStarted: function(i, file, n) {
var node = this.addNode(file).data('file', file);
node.find('.trash').hide();
this.uploads.push(node);
this.progressUpdated(i, file, 0);
},
uploadFinished: function(i, file, response, time, xhr) {
var that = this;
this.uploads.some(function(e) {
if (e.data('file') == file) {
e.find('[name="'+that.options.name+'"]').val(response);
e.find('.progress-bar')
.width('100%')
.attr({'aria-valuenow': 100})
e.find('.trash').show();
e.find('.upload-rate').hide();
setTimeout(function() { e.find('.progress').hide(); }, 600);
return true;
}
});
},
fileSize: function(size) {
var sizes = ['k','M','G','T'],
suffix = '';
while (size > 900) {
size /= 1024;
suffix = sizes.shift();
}
return size.toPrecision(3) + suffix + 'B';
},
addNode: function(file) {
var filenode = $('<div class="file"></div>')
.append($('<div class="filetype"></div>').addClass())
.append($('<div class="filename"></div>').text(file.name)
.append($('<span class="filesize"></span>').text(
this.fileSize(file.size)
)).append($('<div class="upload-rate pull-right"></div>'))
).append($('<div class="progress"></div>')
.append($('<div class="progress-bar"></div>'))
.attr({'aria-valuemin':0,'aria-valuemax':100})
.hide())
.append($('<input type="hidden"/>').attr('name', this.options.name)
.val(file.id));
if (this.options.deletable) {
filenode.prepend($('<span><i class="icon-trash"></i></span>')
.addClass('trash pull-right')
.click($.proxy(this.deleteNode, this, filenode))
);
}
this.$element.parent().find('.files').append(filenode);
return filenode;
},
deleteNode: function(filenode, e) {
if (confirm(__('You sure?')))
filenode.remove();
}
};
$.fn.filedropbox = function ( option ) {
return this.each(function () {
var $this = $(this),
data = $this.data('dropbox'),
options = typeof option == 'object' && option;
if (!data) $this.data('dropbox', (data = new FileDropbox(this, options)));
if (typeof option == 'string') data[option]();
});
};
$.fn.filedropbox.defaults = {
files: [],
deletable: true
};
$.fn.filedropbox.Constructor = FileDropbox;
}(jQuery);
/*
* Default text - jQuery plugin for html5 dragging files from desktop to browser
*
* Author: Weixi Yen
*
* Email: [Firstname][Lastname]@gmail.com
*
* Copyright (c) 2010 Resopollution
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*
* Project home:
* http://www.github.com/weixiyen/jquery-filedrop
*
* Version: 0.1.0
*
* Features:
* Allows sending of extra parameters with file.
* Works with Firefox 3.6+
* Future-compliant with HTML5 spec (will work with Webkit browsers and IE9)
* Usage:
* See README at project homepage
*
*/
;(function($) {
jQuery.event.props.push("dataTransfer");
var default_opts = {
fallback_id: '',
link: false,
url: '',
refresh: 1000,
paramname: 'userfile',
requestType: 'POST', // just in case you want to use another HTTP verb
allowedfileextensions:[],
allowedfiletypes:[],
maxfiles: 25, // Ignored if queuefiles is set > 0
maxfilesize: 1, // MB file size limit
queuefiles: 0, // Max files before queueing (for large volume uploads)
queuewait: 200, // Queue wait time if full
data: {},
headers: {},
drop: empty,
dragStart: empty,
dragEnter: empty,
dragOver: empty,
dragLeave: empty,
docEnter: empty,
docOver: empty,
docLeave: empty,
beforeEach: empty,
afterAll: empty,
rename: empty,
error: function(err, file, i, status) {
alert(err);
},
uploadStarted: empty,
uploadFinished: empty,
progressUpdated: empty,
globalProgressUpdated: empty,
speedUpdated: empty
},
errors = ["BrowserNotSupported", "TooManyFiles", "FileTooLarge", "FileTypeNotAllowed", "NotFound", "NotReadable", "AbortError", "ReadError", "FileExtensionNotAllowed"];
$.fn.filedrop = function(options) {
var opts = $.extend({}, default_opts, options),
global_progress = [],
doc_leave_timer, stop_loop = false,
files_count = 0,
files;
$('#' + opts.fallback_id).css({
display: 'none',
width: 0,
height: 0
});
this.on('drop', drop).on('dragstart', opts.dragStart).on('dragenter', dragEnter).on('dragover', dragOver).on('dragleave', dragLeave);
$(document).on('drop', docDrop).on('dragenter', docEnter).on('dragover', docOver).on('dragleave', docLeave);
(opts.link || this).on('click', function(e){
$('#' + opts.fallback_id).trigger(e);
});
$('#' + opts.fallback_id).change(function(e) {
opts.drop(e);
files = e.target.files;
files_count = files.length;
upload();
});
function drop(e) {
if( opts.drop.call(this, e) === false ) return false;
if(!e.dataTransfer)
return;
files = e.dataTransfer.files;
if (files === null || files === undefined || files.length === 0) {
opts.error(errors[0]);
return false;
}
files_count = files.length;
upload();
e.preventDefault();
return false;
}
function getBuilder(filename, filedata, mime, boundary) {
var dashdash = '--',
crlf = '\r\n',
builder = '',
paramname = opts.paramname;
if (opts.data) {
var params = $.param(opts.data).replace(/\+/g, '%20').split(/&/);
$.each(params, function() {
var pair = this.split("=", 2),
name = decodeURIComponent(pair[0]),
val = decodeURIComponent(pair[1]);
if (pair.length !== 2) {
return;
}
builder += dashdash;
builder += boundary;
builder += crlf;
builder += 'Content-Disposition: form-data; name="' + name + '"';
builder += crlf;
builder += crlf;
builder += val;
builder += crlf;
});
}
if (jQuery.isFunction(paramname)){
paramname = paramname(filename);
}
builder += dashdash;
builder += boundary;
builder += crlf;
builder += 'Content-Disposition: form-data; name="' + (paramname||"") + '"';
builder += '; filename="' + encodeURIComponent(filename) + '"';
builder += crlf;
builder += 'Content-Type: ' + mime;
builder += crlf;
builder += crlf;
builder += filedata;
builder += crlf;
builder += dashdash;
builder += boundary;
builder += dashdash;
builder += crlf;
return builder;
}
function progress(e) {
if (e.lengthComputable) {
var percentage = Math.round((e.loaded * 100) / e.total);
if (this.currentProgress !== percentage) {
this.currentProgress = percentage;
opts.progressUpdated(this.index, this.file, this.currentProgress);
global_progress[this.global_progress_index] = this.currentProgress;
globalProgress();
var elapsed = new Date().getTime();
var diffTime = elapsed - this.currentStart;
if (diffTime >= opts.refresh) {
var diffData = e.loaded - this.startData;
var speed = diffData / diffTime; // KB per second
opts.speedUpdated(this.index, this.file, speed);
this.startData = e.loaded;
this.currentStart = elapsed;
}
}
}
}
function globalProgress() {
if (global_progress.length === 0) {
return;
}
var total = 0, index;
for (index in global_progress) {
if(global_progress.hasOwnProperty(index)) {
total = total + global_progress[index];
}
}
opts.globalProgressUpdated(Math.round(total / global_progress.length));
}
// Respond to an upload
function upload() {
stop_loop = false;
if (!files) {
opts.error(errors[0]);
return false;
}
if (opts.allowedfiletypes.push && opts.allowedfiletypes.length) {
for(var fileIndex = files.length;fileIndex--;) {
if(!files[fileIndex].type || $.inArray(files[fileIndex].type, opts.allowedfiletypes) < 0) {
opts.error(errors[3], files[fileIndex]);
return false;
}
}
}
if (opts.allowedfileextensions.push && opts.allowedfileextensions.length) {
for(var fileIndex = files.length;fileIndex--;) {
var allowedextension = false;
for (i=0;i<opts.allowedfileextensions.length;i++){
if (files[fileIndex].name.substr(files[fileIndex].name.length-opts.allowedfileextensions[i].length).toLowerCase()
== opts.allowedfileextensions[i].toLowerCase()
) {
allowedextension = true;
}
}
if (!allowedextension){
opts.error(errors[8], files[fileIndex]);
return false;
}
}
}
var filesDone = 0,
filesRejected = 0;
if (files_count > opts.maxfiles && opts.queuefiles === 0) {
opts.error(errors[1]);
return false;
}
// Define queues to manage upload process
var workQueue = [];
var processingQueue = [];
var doneQueue = [];
// Add everything to the workQueue
for (var i = 0; i < files_count; i++) {
workQueue.push(i);
}
// Helper function to enable pause of processing to wait
// for in process queue to complete
var pause = function(timeout) {
setTimeout(process, timeout);
return;
};
// Process an upload, recursive
var process = function() {
var fileIndex;
if (stop_loop) {
return false;
}
// Check to see if are in queue mode
if (opts.queuefiles > 0 && processingQueue.length >= opts.queuefiles) {
return pause(opts.queuewait);
} else {
// Take first thing off work queue
fileIndex = workQueue[0];
workQueue.splice(0, 1);
// Add to processing queue
processingQueue.push(fileIndex);
}
try {
if (beforeEach(files[fileIndex]) !== false) {
if (fileIndex === files_count) {
return;
}
var reader = new FileReader(),
max_file_size = 1048576 * opts.maxfilesize;
reader.index = fileIndex;
if (files[fileIndex].size > max_file_size) {
opts.error(errors[2], files[fileIndex], fileIndex);
// Remove from queue
processingQueue.forEach(function(value, key) {
if (value === fileIndex) {
processingQueue.splice(key, 1);
}
});
filesRejected++;
return true;
}
reader.onerror = function(e) {
switch(e.target.error.code) {
case e.target.error.NOT_FOUND_ERR:
opts.error(errors[4]);
return false;
case e.target.error.NOT_READABLE_ERR:
opts.error(errors[5]);
return false;
case e.target.error.ABORT_ERR:
opts.error(errors[6]);
return false;
default:
opts.error(errors[7]);
return false;
};
};
reader.onloadend = !opts.beforeSend ? send : function (e) {
opts.beforeSend(files[fileIndex], fileIndex, function () { send(e); });
};
reader.readAsDataURL(files[fileIndex]);
} else {
filesRejected++;
}
} catch (err) {
// Remove from queue
processingQueue.forEach(function(value, key) {
if (value === fileIndex) {
processingQueue.splice(key, 1);
}
});
opts.error(errors[0]);
return false;
}
// If we still have work to do,
if (workQueue.length > 0) {
process();
}
};
var send = function(e) {
var fileIndex = (e.srcElement || e.target).index;
// Sometimes the index is not attached to the
// event object. Find it by size. Hack for sure.
if (e.target.index === undefined) {
e.target.index = getIndexBySize(e.total);
}
var xhr = new XMLHttpRequest(),
upload = xhr.upload,
file = files[e.target.index],
index = e.target.index,
start_time = new Date().getTime(),
boundary = '------multipartformboundary' + (new Date()).getTime(),
global_progress_index = global_progress.length,
builder,
newName = rename(file.name),
mime = file.type;
if (opts.withCredentials) {
xhr.withCredentials = opts.withCredentials;
}
var data = atob(e.target.result.split(',')[1]);
if (typeof newName === "string") {
builder = getBuilder(newName, data, mime, boundary);
} else {
builder = getBuilder(file.name, data, mime, boundary);
}
upload.index = index;
upload.file = file;
upload.downloadStartTime = start_time;
upload.currentStart = start_time;
upload.currentProgress = 0;
upload.global_progress_index = global_progress_index;
upload.startData = 0;
upload.addEventListener("progress", progress, false);
// Allow url to be a method
if (jQuery.isFunction(opts.url)) {
xhr.open(opts.requestType, opts.url(), true);
} else {
xhr.open(opts.requestType, opts.url, true);
}
xhr.setRequestHeader('content-type', 'multipart/form-data; boundary=' + boundary);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
// Add headers
$.each(opts.headers, function(k, v) {
xhr.setRequestHeader(k, v);
});
xhr.sendAsBinary(builder);
global_progress[global_progress_index] = 0;
globalProgress();
opts.uploadStarted(index, file, files_count);
xhr.onload = function() {
var serverResponse = null;
if (xhr.responseText) {
try {
serverResponse = jQuery.parseJSON(xhr.responseText);
}
catch (e) {
serverResponse = xhr.responseText;
}
}
var now = new Date().getTime(),
timeDiff = now - start_time,
result = opts.uploadFinished(index, file, serverResponse, timeDiff, xhr);
filesDone++;
// Remove from processing queue
processingQueue.forEach(function(value, key) {
if (value === fileIndex) {
processingQueue.splice(key, 1);
}
});
// Add to donequeue
doneQueue.push(fileIndex);
// Make sure the global progress is updated
global_progress[global_progress_index] = 100;
globalProgress();
if (filesDone === (files_count - filesRejected)) {
afterAll();
}
if (result === false) {
stop_loop = true;
}
// Pass any errors to the error option
if (xhr.status < 200 || xhr.status > 299) {
opts.error(xhr.statusText, file, fileIndex, xhr.status);
}
};
};
// Initiate the processing loop
process();
}
function getIndexBySize(size) {
for (var i = 0; i < files_count; i++) {
if (files[i].size === size) {
return i;
}
}
return undefined;
}
function rename(name) {
return opts.rename(name);
}
function beforeEach(file) {
return opts.beforeEach(file);
}
function afterAll() {
return opts.afterAll();
}
function dragEnter(e) {
clearTimeout(doc_leave_timer);
e.preventDefault();
opts.dragEnter.call(this, e);
}
function dragOver(e) {
clearTimeout(doc_leave_timer);
e.preventDefault();
opts.docOver.call(this, e);
opts.dragOver.call(this, e);
}
function dragLeave(e) {
clearTimeout(doc_leave_timer);
opts.dragLeave.call(this, e);
e.stopPropagation();
}
function docDrop(e) {
e.preventDefault();
opts.docLeave.call(this, e);
return false;
}
function docEnter(e) {
clearTimeout(doc_leave_timer);
e.preventDefault();
opts.docEnter.call(this, e);
return false;
}
function docOver(e) {
clearTimeout(doc_leave_timer);
e.preventDefault();
opts.docOver.call(this, e);
return false;
}
function docLeave(e) {
doc_leave_timer = setTimeout((function(_this) {
return function() {
opts.docLeave.call(_this, e);
};
})(this), 200);
}
return this;
};
function empty() {}
try {
if (XMLHttpRequest.prototype.sendAsBinary) {
return;
}
XMLHttpRequest.prototype.sendAsBinary = function(datastr) {
function byteValue(x) {
return x.charCodeAt(0) & 0xff;
}
var ords = Array.prototype.map.call(datastr, byteValue);
var ui8a = new Uint8Array(ords);
// Not pretty: Chrome 22 deprecated sending ArrayBuffer, moving instead
// to sending ArrayBufferView. Sadly, no proper way to detect this
// functionality has been discovered. Happily, Chrome 22 also introduced
// the base ArrayBufferView class, not present in Chrome 21.
if ('ArrayBufferView' in window)
this.send(ui8a);
else
this.send(ui8a.buffer);
};
} catch (e) {}
})(jQuery);
......@@ -55,7 +55,9 @@ $dispatcher = patterns('',
url_get('^help-topic/(?P<id>\d+)$', 'getFormsForHelpTopic'),
url_get('^field-config/(?P<id>\d+)$', 'getFieldConfiguration'),
url_post('^field-config/(?P<id>\d+)$', 'saveFieldConfiguration'),
url_delete('^answer/(?P<entry>\d+)/(?P<field>\d+)$', 'deleteAnswer')
url_delete('^answer/(?P<entry>\d+)/(?P<field>\d+)$', 'deleteAnswer'),
url_post('^upload/(\d+)?$', 'upload'),
url_post('^upload/(\w+)?$', 'attach')
)),
url('^/list/', patterns('ajax.forms.php:DynamicFormsAjaxAPI',
url_get('^(?P<list>\w+)/item/(?P<id>\d+)/properties$', 'getListItemProperties'),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment