Index: admin/js/_media.js
===================================================================
--- admin/js/_media.js	(revision 1140)
+++ admin/js/_media.js	(revision 1144)
@@ -1,91 +1,36 @@
 $(function() {
-	// Replace remove links by a POST on hidden form
-	fileRemoveAct();
-	
-	function fileRemoveAct() {
-		$('a.media-remove').click(function() {
-			var m_name = $(this).parents('ul').find('li:first>a').text();
-			if (window.confirm(dotclear.msg.confirm_delete_media.replace('%s',m_name))) {
-				var f = $('#media-remove-hide').get(0);
-				f.elements['remove'].value = this.href.replace(/^(.*)&remove=(.*?)(&|$)/,'$2');
-				this.href = '';
-				f.submit();
-			}
-			return false;
-		});
-	}
-	
-	if (!$.browser.opera) {
-		var upldr = $('<a href="#" id="cu-enable">' + dotclear.msg.activate_enhanced_uploader + '</a>').click( function() {
-			candyUploadInit();
-			return false;
-		});
-		$('#media-upload').append($('<div></div>').append(upldr));
-		if ((dotclear.candyUpload_force_init == '1') || (($.cookie('dc_candy_upl') == 1))) {
-			candyUploadInit();
-		}
-	}
-	
-	function candyUploadInit()
-	{
-		var candy_upload_success = false;
-		var candy_upload_form_url = $('#media-upload').attr('action') + '&file_sort=date-desc&d=' + $('#media-upload input[name=d]').val();
-		var candy_upload_limit = $('#media-upload input[name=MAX_FILE_SIZE]').val();
-		$('#media-upload').candyUpload({
-			upload_url: dotclear.candyUpload.base_url + '/media.php',
-			flash_movie: dotclear.candyUpload.movie_url,
-			file_size_limit: candy_upload_limit + 'b',
-			params: 'swfupload=1&amp;' + dotclear.candyUpload.params,
-			
-			callbacks: {
-				createControls: function() {
-					var _this = this;
-					this.ctrl.btn_browse.hide();
-					this.ctrl.msg.html(dotclear.msg.load_enhanced_uploader);
-					var l = $('<a href="#">' + dotclear.msg.disable_enhanced_uploader + '</a>').click(function() {
-						_this.upldr.destroy();
-						_this.ctrl.block.empty().remove();
-						$('#media-upload').show();
-						delete _this;
-						$.cookie('dc_candy_upl','',{expires:-1});
-						return false;
-					});
-					this.ctrl.disable = $('<div class="cu-disable"></div>').append(l).appendTo(this.ctrl.block);
-				},
-				flashReady: function() {
-					var _this = this;
-					this.ctrl.msg.fadeOut('fast',function() {
-						$(this).text(_this.locales.no_file_in_queue).fadeIn('fast');
-						_this.ctrl.btn_browse.fadeIn('fast',function() {
-							_this.upldr.container.children().css({
-								width: $('.cu-btn-browse').width(),
-								height: $('.cu-btn-browse').height()
-							});
-						});
-					});
-				},
-				uploadSuccess: function(o,data) {
-					if (data == 'ok') {
-						candy_upload_success = true;
-						this.fileMsg(o.id,this.locales.file_uploaded);
-					} else {
-						this.fileErrorMsg(o.id,data);
-					}
-					
-					// uploads finished and at least one success
-					if (candy_upload_success && $('div.cu-file:has(span.cu-filecancel a)',this.ctrl.files).length == 0) {
-						$.cookie('dc_candy_upl','1',{expires: 30});
-						$.get(candy_upload_form_url,function(data) {
-							var media = $('div.media-list');
-							media.after($('div.media-list',data)).remove();
-							fileRemoveAct();
-						});
-					}
-				},
-				fileQueued: function() {
-					;
-				}
-			}
-		});
-	}
+    $('#fileupload')
+	.fileupload({
+	    url: $('#fileupload').attr('action'),
+	    autoUpload: false
+	});
+
+    // Load existing files:
+    $('#fileupload').addClass('fileupload-processing');
+    $.ajax({
+        url: $('#fileupload').fileupload('option', 'url'),
+        dataType: 'json',
+        context: $('#fileupload')[0]
+    }).always(function (result) {
+        $(this).removeClass('fileupload-processing');
+    }).done(function (result) {
+        $(this).fileupload('option', 'done')
+            .call(this, null, {result: result});
+    });
+
+    // Replace remove links by a POST on hidden form
+    fileRemoveAct();
+
+    function fileRemoveAct() {
+	$('a.media-remove').click(function() {
+	    var m_name = $(this).parents('ul').find('li:first>a').text();
+	    if (window.confirm(dotclear.msg.confirm_delete_media.replace('%s',m_name))) {
+		var f = $('#media-remove-hide').get(0);
+		f.elements['remove'].value = this.href.replace(/^(.*)&remove=(.*?)(&|$)/,'$2');
+		this.href = '';
+		f.submit();
+	    }
+	    return false;
+	});
+    }
 });
Index: dmin/js/jquery/jquery.candyUpload.js
===================================================================
--- admin/js/jquery/jquery.candyUpload.js	(revision 536)
+++ 	(revision )
@@ -1,644 +1,0 @@
-(function($) {
-	// We need to create a global SWFUpload object to store instances
-	window.SWFUpload = {
-		instances: new Array(),
-		movieCount: 0,
-		version: "2.2.0 Beta 2",
-		QUEUE_ERROR: {
-			QUEUE_LIMIT_EXCEEDED:	-100,
-			FILE_EXCEEDS_SIZE_LIMIT:	-110,
-			ZERO_BYTE_FILE:		-120,
-			INVALID_FILETYPE:		-130
-		},
-		UPLOAD_ERROR: {
-			HTTP_ERROR:				-200,
-			MISSING_UPLOAD_URL:			-210,
-			IO_ERROR:					-220,
-			SECURITY_ERROR:			-230,
-			UPLOAD_LIMIT_EXCEEDED:		-240,
-			UPLOAD_FAILED:				-250,
-			SPECIFIED_FILE_ID_NOT_FOUND:	-260,
-			FILE_VALIDATION_FAILED:		-270,
-			FILE_CANCELLED:			-280,
-			UPLOAD_STOPPED:			-290
-		},
-		FILE_STATUS: {
-			QUEUED:		-1,
-			IN_PROGRESS:	-2,
-			ERROR:		-3,
-			COMPLETE:		-4,
-			CANCELLED:	-5
-		},
-		BUTTON_ACTION: {
-			SELECT_FILE:	-100,
-			SELECT_FILES:	-110,
-			START_UPLOAD:	-120
-		},
-		CURSOR: {
-			ARROW:	-1,
-			HAND:	-2
-		},
-		WINDOW_MODE: {
-			WINDOW:		'window',
-			TRANSPARENT:	'transparent',
-			OPAQUE:		'opaque'
-		}
-	};
-	
-	$.uploader = function(settings,callbacks) {
-		return new $._uploader(settings,callbacks);
-	};
-	
-	$._uploader = function(settings,callbacks) {
-		var defaults = {
-			debug: false,
-			upload_url: '',
-			flash_movie: '',
-			movie_name: null,
-			params: null,
-			use_query_string: null,
-			requeue_on_error: false,
-			file_types: '*.*',
-			file_types_description: 'All files',
-			file_size_limit: 0,
-			file_upload_limit: 0,
-			file_queue_limit: -1,
-			target_element: null
-		};
-		this.params = $.extend(defaults,settings);
-		
-		var default_callbacks = {
-			flashReady: function() {},
-			fileDialogComplete: function() {},
-			fileDialogStart: function() {},
-			fileQueued: function() {},
-			fileQueueError: function() {},
-			uploadStart: function() {},
-			uploadProgress: function() {},
-			uploadError: function() {},
-			uploadSuccess: function() {},
-			uploadComplete: function() {},
-			debug: function() {}
-		};
-		this.callbacks = $.extend(default_callbacks,callbacks);
-		
-		return this.build();
-	};
-	
-	$._uploader.prototype = {
-		log: function(msg) {
-			if (!this.params.debug) { return; }
-			try {
-				console.log(msg);
-			} catch(e) {}
-		},
-		
-		/* Constructor */
-		build: function(settings) {
-			if (!this.params.upload_url || !this.params.movie_name) {
-				throw('Configuration error. Please make sure you specified movie_name and upload_url options.');
-			}
-			
-			if ($.browser.opera) {
-				return this;
-			}
-			
-			// Get a real bytes size limit (if we want to reuse it somewhere)
-			this.params.file_size_limit = this.getFileSizeLimit(this.params.file_size_limit);
-			
-			// Flash vars
-			var fv = new Array();
-			this.addFlashVar(fv,'debugEnabled',this.params.debug);
-			this.addFlashVar(fv,'movieName',this.params.movie_name);
-			this.addFlashVar(fv,'uploadURL',this.params.upload_url);
-			this.addFlashVar(fv,'fileTypes',this.params.file_types);
-			this.addFlashVar(fv,'fileTypesDescription',this.params.file_types_description);
-			this.addFlashVar(fv,'params',this.params.params);
-			this.addFlashVar(fv,'fileSizeLimit',this.params.file_size_limit + 'b');
-			this.addFlashVar(fv,'fileUploadLimit',this.params.file_upload_limit);
-			this.addFlashVar(fv,'fileQueueLimit',this.params.file_queue_limit);
-			this.addFlashVar(fv,'requeueOnError',this.params.requeue_on_error);
-			this.addFlashVar(fv,'buttonImageURL','');
-			this.addFlashVar(fv,'buttonWidth',500);
-			this.addFlashVar(fv,'buttonHeight',500);
-			this.addFlashVar(fv,'buttonText','');
-			this.addFlashVar(fv,'buttonTextTopPadding',0);
-			this.addFlashVar(fv,'buttonTextLeftPadding',0);
-			this.addFlashVar(fv,'buttonTextStyle','');
-			this.addFlashVar(fv,'buttonAction',this.params.upload_limit == 1 ? -100 : -110);
-			this.addFlashVar(fv,'buttonDisabled',false);
-			this.addFlashVar(fv,'buttonCursor',-2);
-			this.addFlashVar(fv,'button_window_mode','window');
-			
-			// Create SWFUpload Instance
-			this.flashBind();
-			
-			// Flash object
-			var flash =
-			'<object id="' + this.params.movie_name + '" data="' + this.params.flash_movie + '" ' +
-			'type="application/x-shockwave-flash" width="10" height="10">' +
-			'<param name="movie" value="' + this.params.flash_movie + '" />'+
-			'<param name="wmode" value="transparent" />'+
-			'<param name="menu" value="false" />'+
-			'<param name="allowScriptAccess" value="always" />'+
-			'<param name="flashvars" value="' + fv.join('&amp;') + '" />'+
-			'</object>';
-			
-			// Flash container
-			this.container = $('<span></span>');
-			if (this.params.target_element == null) {
-				this.container.css({
-					display: 'block',
-					position: 'absolute',
-					left: $(document).scrollLeft()+'px',
-					top: $(document).scrollTop()+'px',
-					width: '30px',
-					height: '30px',
-					background: '#f00'
-				});
-				
-				$('body').append(this.container);
-			} else {
-				$(this.params.target_element).append(this.container);
-				this.container.css({
-					display: 'block',
-					position: 'absolute',
-					top: 0,
-					left: 0,
-					zIndex: 1
-				});
-			}
-			this.container[0].innerHTML = flash;
-			this.movie = document.getElementById(this.params.movie_name);
-			
-			return this;
-		},
-		
-		addFlashVar: function(fv,n,v) {
-			if (v != null) {
-				fv.push(n + '=' + encodeURIComponent(v));
-			}
-		},
-		
-		// Bind flash events to callbacks.events
-		flashBind: function() {
-			_this = this;
-			var events = {
-				flashReady: function() {
-					if (window[_this.params.movie_name] == undefined) {
-						window[_this.params.movie_name] = _this.movie;
-					}
-					_this.flashBindEvent('flashReady',arguments);
-				},
-				fileDialogComplete: function() { _this.flashBindEvent('fileDialogComplete',arguments); },
-				fileDialogStart: function() { _this.flashBindEvent('fileDialogStart',arguments); },
-				fileQueued: function(file_object) { _this.flashBindEvent('fileQueued',arguments); },
-				fileQueueError: function(file_object,error_code,error_msg) { _this.flashBindEvent('fileQueueError',arguments); },
-				uploadStart: function() { _this.flashBindEvent('uploadStart',arguments); },
-				uploadProgress: function() { _this.flashBindEvent('uploadProgress',arguments); },
-				uploadError: function() { _this.flashBindEvent('uploadError',arguments); },
-				uploadSuccess: function() { _this.flashBindEvent('uploadSuccess',arguments); },
-				uploadComplete: function() { _this.flashBindEvent('uploadComplete',arguments); },
-				debug: function() { _this.flashBindEvent('debug',arguments); }
-			};
-			window.SWFUpload.instances[this.params.movie_name] = events;
-			window.SWFUpload.movieCount++;
-		},
-		
-		// Each flash event is called as callbacks.events([arg],...) with this = uploader object
-		flashEventQueue: [],
-		flashBindEvent: function(evt,a) {
-			a = a || new Array();
-			
-			var _this = this;
-			if ($.isFunction(this.callbacks[evt])) {
-				// Queue the event
-				this.flashEventQueue.push(function () {
-						this.callbacks[evt].apply(this,a);
-				});
-				
-				// Execute the next queued event
-				setTimeout(function () {
-					_this.flashExecuteNextEvent();
-				},0);
-			} else if (this.callbacks[evt] !== null) {
-				throw 'Event handler ' + evt + ' is unknown or is not a function';
-			}
-
-		},
-		flashExecuteNextEvent: function() {
-			var f = this.flashEventQueue ? this.flashEventQueue.shift() : null;
-			if ($.isFunction(f)) {
-				f.apply(this);
-			}
-		},
-		
-		destroy: function() {
-			try {
-				this.StopUpload();
-				$(this.movie).remove();
-				SWFUpload.instances[this.params.movie_name] = null;
-				SWFUpload.movieCount--;
-				delete SWFUpload.instances[this.movieName];
-				delete window[this.movieName];
-				
-				return true;
-			} catch(e) {
-				return false;
-			}
-		},
-		
-		// Flash uploader functions
-		StartUpload: function(file_id) {
-			return this.movie.StartUpload(file_id);
-		},
-		ReturnUploadStart: function(value) {
-			return this.movie.ReturnUploadStart(value);
-		},
-		StopUpload: function() {
-			return this.movie.StopUpload();
-		},
-		CancelUpload: function(file_id) {
-			return this.movie.CancelUpload(file_id);
-		},
-		GetStats: function() {
-			return this.movie.GetStats();
-		},
-		SetStats: function(stats) {
-			return this.movie.SetStats(stats);
-		},
-		GetFile: function(file_id) {
-			return this.movie.GetFile(file_id);
-		},
-		GetFileByIndex: function(file_index) {
-			return this.movie.GetFileByIndex(file_index);
-		},
-		
-		// Size formater, that's better
-		getFileSizeLimit: function(size) {
-			var value = 0;
-			var unit = 'kb';
-			
-			size = $.trim(size.toLowerCase());
-			
-			var values = size.match(/^\d+/);
-			if (values != null && values.length > 0) {
-				value = parseInt(values[0]);
-			}
-			
-			var units = size.match(/(b|kb|mb|gb)/);
-			if (units != null && units.length > 0) {
-				unit = units[0];
-			}
-			
-			var multiplier = 1024;
-			if (unit === "b") {
-				multiplier = 1;
-			} else if (unit === "mb") {
-				multiplier = 1048576;
-			} else if (unit === "gb") {
-				multiplier = 1073741824;
-			}
-			
-			return value * multiplier;
-		}
-	};
-})(jQuery);
-
-(function($) {
-	$.fn.candyUpload = function(settings,callbacks) {
-		new $._candyUpload(this,settings,callbacks);
-		return this;
-	};
-	
-	$._candyUpload = function(target,settings,callbacks) {
-		var defaults = {
-			debug: false,
-			upload_url: '',
-			params: null,
-			flash_movie: '',
-			file_types: '*.*',
-			file_types_description: 'All files',
-			file_size_limit: 0,
-			file_upload_limit: 0,
-			file_queue_limit: -1,
-			
-			callbacks: {} // Put here all callbacks you want, named as in $.uploader events
-		};
-		this.params = $.extend(defaults,settings);
-		this.params.movie_name = 'SWFU-' + (window.SWFUpload.instances.length + 1);
-		this.target = target;
-		
-		// Create controls
-		this.createControls();
-		
-		this.target.hide().after(this.ctrl.block).hide();
-		this.params.target_element = this.ctrl.btn_browse.parent().get(0);
-		
-		// Uploader init
-		var _this = this;
-		this.upldr = $.uploader(this.params,{
-			debug: function(msg) {
-				$('body').append('<pre class="debug">' + msg + '</pre>');
-				_this.bindEvent('debug',arguments);
-			},
-			flashReady: function() {
-				_this.initControls(this);
-				_this.bindEvent('flashReady',arguments);
-				
-				this.movie.style.width = _this.ctrl.btn_browse.width()+'px';
-				this.movie.style.height = _this.ctrl.btn_browse.height()+'px';
-			},
-			fileDialogComplete: function(num_ref_files,num_queue_files) {
-				_this.bindEvent('fileDialogComplete',arguments);
-			},
-			fileDialogStart: function() {
-				_this.bindEvent('fileQueued',arguments);
-			},
-			fileQueued: function(o) {
-				_this.appendFile(this,o);
-				_this.refreshControls(this);
-				_this.bindEvent('fileQueued',arguments);
-			},
-			fileQueueError: function(o,code,msg) {
-				var codes = window.SWFUpload.QUEUE_ERROR;
-				switch (code) {
-					case codes.QUEUE_LIMIT_EXCEEDED:
-						_this.queueErrorMsg(_this.locales.limit_exceeded);
-						break;
-					case codes.FILE_EXCEEDS_SIZE_LIMIT:
-						_this.queueErrorMsg(_this.locales.size_limit_exceeded);
-						break;
-					case codes.ZERO_BYTE_FILE:
-					case codes.INVALID_FILETYPE:
-						_this.queueErrorMsg(msg);
-						break;
-				}
-				_this.bindEvent('fileQueueError',arguments);
-			},
-			uploadStart: function() {
-				this.ReturnUploadStart(true);
-				_this.bindEvent('uploadStart',arguments);
-			},
-			uploadProgress: function(o,bytes,total) {
-				_this.fileProgressBar(o.id,bytes,total);
-				_this.bindEvent('uploadProgress',arguments);
-			},
-			uploadError: function(o,code,msg) {
-				var codes = window.SWFUpload.UPLOAD_ERROR;
-				switch (code) {
-					case codes.FILE_CANCELLED:
-						_this.fileErrorMsg(o.id,_this.locales.canceled);
-						break;
-					case codes.HTTP_ERROR:
-						_this.fileErrorMsg(o.id,_this.locales.http_error + ' ' + msg);
-						break;
-					case codes.MISSING_UPLOAD_URL:
-					case codes.IO_ERROR:
-					case codes.SECURITY_ERROR:
-					case codes.UPLOAD_LIMIT_EXCEEDED:
-					case codes.UPLOAD_FAILED:
-					case codes.SPECIFIED_FILE_ID_NOT_FOUND:
-					case codes.FILE_VALIDATION_FAILED:
-					case codes.FILE_CANCELLED:
-					case codes.UPLOAD_STOPPED:
-						_this.fileErrorMsg(o.id,_this.locales.error + ' ' + msg);
-						break;
-				}
-				_this.refreshControls(this);
-				_this.removeFileCancel(o);
-				_this.bindEvent('uploadError',arguments);
-			},
-			uploadSuccess: function(o,data) {
-				_this.fileProgressBar(o.id,1,1);
-				_this.refreshControls(this);
-				_this.removeFileCancel(o);
-				_this.bindEvent('uploadSuccess',arguments);
-			},
-			uploadComplete: function(o) {
-				// Once completed, start next queued upload
-				this.StartUpload();
-				_this.refreshControls(this);
-				_this.bindEvent('uploadComplete',arguments);
-			}
-		});
-	};
-	
-	$._candyUpload.prototype = {
-		locales: {
-			max_file_size: 'Maximum file size allowed:',
-			limit_exceeded: 'Limit exceeded.',
-			size_limit_exceeded: 'File size exceeds allowed limit.',
-			canceled: 'Canceled.',
-			http_error: 'HTTP Error:',
-			error: 'Error:',
-			choose_file: 'Choose file',
-			choose_files: 'Choose files',
-			cancel: 'Cancel',
-			clean: 'Clean',
-			upload: 'Upload',
-			no_file_in_queue: 'No file in queue.',
-			file_in_queue: '1 file in queue.',
-			files_in_queue: '%d files in queue.',
-			queue_error: 'Queue error:'
-		},
-		ctrl: {
-			block: $('<div class="cu-ctrl"></div>'),
-			files: null
-		},
-		
-		createControls: function() {
-			this.ctrl.btn_browse = $('<a href="#" class="button">&nbsp;</a>').click(function() {
-				return false;
-			});
-			
-			this.ctrl.btn_cancel = $('<a href="#" class="button">' + this.locales.cancel + '</a>').click(function() {
-				return false;
-			});
-			
-			this.ctrl.btn_clean = $('<a href="#" class="button">' + this.locales.clean + '</a>').click(function() {
-				return false;
-			});
-			
-			this.ctrl.btn_upload = $('<a href="#" class="button">' + this.locales.upload + '</a>').click(function() {
-				return false;
-			});
-			
-			this.ctrl.msg = $('<div class="cu-msg">' + this.locales.no_file_in_queue + '</div>').appendTo(this.ctrl.block);
-			
-			var btn = $('<div class="cu-btn"></div>').appendTo(this.ctrl.block);
-			var brw = $('<span class="cu-btn-browse"></span>').append(this.ctrl.btn_browse).appendTo(btn);
-			$('<span class="cu-btn-upload"></span>').append(this.ctrl.btn_upload).appendTo(btn).hide();
-			$('<span class="cu-btn-cancel"></span>').append(this.ctrl.btn_cancel).appendTo(btn).hide();
-			$('<span class="cu-btn-clean"></span>').append(this.ctrl.btn_clean).appendTo(btn).hide();
-			
-			this.bindEvent('createControls');
-		},
-		
-		initControls: function(upldr) {
-			if (this.params.file_queue_limit == 1) {
-				this.ctrl.btn_browse.text(this.locales.choose_file);
-			} else {
-				this.ctrl.btn_browse.text(this.locales.choose_files);
-			}
-			
-			var _this = this;
-			
-			this.ctrl.btn_cancel.click(function() {
-				_this.cancelQueue(upldr);
-				return false;
-			});
-			
-			this.ctrl.btn_clean.click(function() {
-				_this.cleanQueue(upldr);
-				return false;
-			});
-			
-			this.ctrl.btn_upload.click(function() {
-				_this.uploadQueue(upldr);
-				return false;
-			});
-			
-			var size = this.formatSize(upldr.params.file_size_limit);
-			$('<p class="cu-maxsize form-note info">' + this.locales.max_file_size + ' ' + size + '</p>').appendTo(this.ctrl.block);
-		},
-		
-		refreshControls: function(upldr) {
-			if (!this.ctrl.files || this.ctrl.files.length == 0) {
-				return;
-			}
-			
-			var stats = upldr.GetStats();
-			
-			if (stats.files_queued > 0) {
-				this.ctrl.btn_cancel.parent().show();
-				this.ctrl.btn_upload.parent().show();
-				if (this.params.file_queue_limit > 0 && this.params.file_queue_limit == stats.files_queued) {
-					this.ctrl.btn_browse.hide();
-				} else {
-					this.ctrl.btn_browse.show();
-				}
-				if (stats.files_queued > 1) {
-					var msg = this.locales.files_in_queue.replace(/%d/,stats.files_queued);
-				} else {
-					var msg = this.locales.file_in_queue;
-				}
-			} else {
-				this.ctrl.btn_browse.show();
-				this.ctrl.btn_cancel.parent().hide();
-				this.ctrl.btn_upload.parent().hide();
-				var msg = this.locales.no_file_in_queue;
-			}
-			
-			this.ctrl.msg.removeClass('cu-error').text(msg);
-			
-			if (stats.successful_uploads > 0 || stats.upload_errors > 0 || stats.upload_cancelled > 0) {
-				this.ctrl.btn_clean.parent().show();
-			} else {
-				this.ctrl.btn_clean.parent().hide();
-			}
-		},
-		
-		removeFileCancel: function(o) {
-			$('#' + o.id + ' span.cu-filecancel',this.ctrl.files).remove();
-		},
-		
-		appendFile: function(upldr,o) {
-			if (!this.ctrl.files) {
-				this.ctrl.files = $('<div class="cu-files"></div>');
-				this.ctrl.msg.after(this.ctrl.files);
-			}
-			
-			var fileblock = $('<div class="cu-file" id="' + o.id + '">' +
-					'<div class="cu-fileinfo"><span class="cu-filename">' + o.name + '</span> ' + 
-					'<span class="cu-filesize">(' + this.formatSize(o.size) + ')</span> ' +
-					'<span class="cu-filecancel"><a href="#">cancel</a> </span> ' +
-					'<span class="cu-filemsg"></span>' +
-					'</div>');
-			
-			$('span.cu-filecancel a',fileblock).click(function() {
-				upldr.CancelUpload(o.id);
-				return false;
-			});
-			this.ctrl.files.append(fileblock);
-		},
-		
-		fileProgressBar: function(file_id,bytes,total) {
-			var bar = $('#' + file_id + ' div.cu-progress>div',this.ctrl.files);
-			if (bar.length == 0) {
-				$('#' + file_id,this.ctrl.files).append('<div class="cu-progress"><div>&nbsp;</div></div>');
-				bar = $('#' + file_id + ' div.cu-progress>div',this.ctrl.files);
-			}
-			
-			var percent = Math.round((bytes * 100) / total);
-			bar.css('width',percent+'%').text(percent + '%');
-		},
-		
-		fileMsg: function(file_id,msg,error) {
-			error = error || false;
-			var span = $('#' + file_id + ' span.cu-filemsg',this.ctrl.files).attr('class','cu-filemsg');
-			if (error) {
-				span.addClass('cu-error');
-			}
-			span.text(msg);
-		},
-		
-		fileErrorMsg: function(file_id,msg) {
-			this.fileMsg(file_id,msg,true);
-		},
-		
-		cancelQueue: function(upldr) {
-			if (!this.ctrl.files || this.ctrl.files.length == 0) {
-				return;
-			}
-			
-			this.ctrl.files.children('div').each(function() {
-				upldr.CancelUpload(this.id);
-			});
-		},
-		
-		uploadQueue: function(upldr) {
-			if (!this.ctrl.files || this.ctrl.files.length == 0) {
-				return;
-			}
-			
-			upldr.StartUpload();
-		},
-		
-		cleanQueue: function(upldr) {
-			var _this = this;
-			var e = $('div.cu-file',this.ctrl.files).not(':has(span.cu-filecancel a)');
-			
-			e.filter(':last').slideUp(200,function() {
-				$(this).remove();
-				if (e.length == 1) {
-					upldr.SetStats({successful_uploads:0, upload_errors:0, upload_cancelled:0});
-					_this.refreshControls(upldr);
-				} else if (e.length > 1) {
-					_this.cleanQueue(upldr);
-				}
-			});
-		},
-		
-		queueErrorMsg: function(msg) {
-			this.ctrl.msg.addClass('cu-error').text(this.locales.queue_error + ' ' + msg);
-		},
-		
-		formatSize: function(s) {
-			var a_size = Array('B', 'KB', 'MB', 'GB', 'TB');
-			var i_index = 0;
-			while (s > 1024) {
-				i_index++;
-				s/=1024;
-			}
-			return (Math.round(s * 100) /100) + ' ' + a_size[i_index];
-		},
-		
-		bindEvent: function(evt,a) {
-			if (this.params.callbacks[evt] != undefined && $.isFunction(this.params.callbacks[evt])) {
-				a = a || new Array();
-				this.params.callbacks[evt].apply(this,a);
-			}
-		}
-	};
-})(jQuery);
Index: admin/js/jsUpload/jquery.fileupload-process.js
===================================================================
--- admin/js/jsUpload/jquery.fileupload-process.js	(revision 1144)
+++ admin/js/jsUpload/jquery.fileupload-process.js	(revision 1144)
@@ -0,0 +1,158 @@
+/*
+ * jQuery File Upload Processing Plugin 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
+ */
+
+/*jslint nomen: true, unparam: true */
+/*global define, window */
+
+(function (factory) {
+    'use strict';
+    if (typeof define === 'function' && define.amd) {
+        // Register as an anonymous AMD module:
+        define([
+            'jquery',
+            './jquery.fileupload'
+        ], factory);
+    } else {
+        // Browser globals:
+        factory(
+            window.jQuery
+        );
+    }
+}(function ($) {
+    'use strict';
+
+    var originalAdd = $.blueimp.fileupload.prototype.options.add;
+
+    // The File Upload Processing plugin extends the fileupload widget
+    // with file processing functionality:
+    $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+
+        options: {
+            // The list of processing actions:
+            processQueue: [
+                /*
+                {
+                    action: 'log',
+                    type: 'debug'
+                }
+                */
+            ],
+            add: function (e, data) {
+                var $this = $(this);
+                data.process(function () {
+                    return $this.fileupload('process', data);
+                });
+                originalAdd.call(this, e, data);
+            }
+        },
+
+        processActions: {
+            /*
+            log: function (data, options) {
+                console[options.type](
+                    'Processing "' + data.files[data.index].name + '"'
+                );
+            }
+            */
+        },
+
+        _processFile: function (data) {
+            var that = this,
+                dfd = $.Deferred().resolveWith(that, [data]),
+                chain = dfd.promise();
+            this._trigger('process', null, data);
+            $.each(data.processQueue, function (i, settings) {
+                var func = function (data) {
+                    return that.processActions[settings.action].call(
+                        that,
+                        data,
+                        settings
+                    );
+                };
+                chain = chain.pipe(func, settings.always && func);
+            });
+            chain
+                .done(function () {
+                    that._trigger('processdone', null, data);
+                    that._trigger('processalways', null, data);
+                })
+                .fail(function () {
+                    that._trigger('processfail', null, data);
+                    that._trigger('processalways', null, data);
+                });
+            return chain;
+        },
+
+        // Replaces the settings of each processQueue item that
+        // are strings starting with an "@", using the remaining
+        // substring as key for the option map,
+        // e.g. "@autoUpload" is replaced with options.autoUpload:
+        _transformProcessQueue: function (options) {
+            var processQueue = [];
+            $.each(options.processQueue, function () {
+                var settings = {};
+                $.each(this, function (key, value) {
+                    if ($.type(value) === 'string' &&
+                            value.charAt(0) === '@') {
+                        settings[key] = options[value.slice(1)];
+                    } else {
+                        settings[key] = value;
+                    }
+                });
+                processQueue.push(settings);
+            });
+            options.processQueue = processQueue;
+        },
+
+        // Returns the number of files currently in the processsing queue:
+        processing: function () {
+            return this._processing;
+        },
+
+        // Processes the files given as files property of the data parameter,
+        // returns a Promise object that allows to bind callbacks:
+        process: function (data) {
+            var that = this,
+                options = $.extend({}, this.options, data);
+            if (options.processQueue && options.processQueue.length) {
+                this._transformProcessQueue(options);
+                if (this._processing === 0) {
+                    this._trigger('processstart');
+                }
+                $.each(data.files, function (index, file) {
+                    var opts = index ? $.extend({}, options) : options,
+                        func = function () {
+                            return that._processFile(opts);
+                        };
+                    opts.index = index;
+                    that._processing += 1;
+                    that._processingQueue = that._processingQueue.pipe(func, func)
+                        .always(function () {
+                            that._processing -= 1;
+                            if (that._processing === 0) {
+                                that._trigger('processstop');
+                            }
+                        });
+                });
+            }
+            return this._processingQueue;
+        },
+
+        _create: function () {
+            this._super();
+            this._processing = 0;
+            this._processingQueue = $.Deferred().resolveWith(this)
+                .promise();
+        }
+
+    });
+
+}));
Index: admin/js/jsUpload/jquery.fileupload-resize.js
===================================================================
--- admin/js/jsUpload/jquery.fileupload-resize.js	(revision 1144)
+++ admin/js/jsUpload/jquery.fileupload-resize.js	(revision 1144)
@@ -0,0 +1,212 @@
+/*
+ * jQuery File Upload Image Resize Plugin 1.1.2
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, 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 */
+
+(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-process'
+        ], factory);
+    } else {
+        // Browser globals:
+        factory(
+            window.jQuery,
+            window.loadImage
+        );
+    }
+}(function ($, loadImage) {
+    'use strict';
+
+    // Prepend to the default processQueue:
+    $.blueimp.fileupload.prototype.options.processQueue.unshift(
+        {
+            action: 'loadImage',
+            fileTypes: '@loadImageFileTypes',
+            maxFileSize: '@loadImageMaxFileSize',
+            noRevoke: '@loadImageNoRevoke',
+            disabled: '@disableImageLoad'
+        },
+        {
+            action: 'resizeImage',
+            maxWidth: '@imageMaxWidth',
+            maxHeight: '@imageMaxHeight',
+            minWidth: '@imageMinWidth',
+            minHeight: '@imageMinHeight',
+            crop: '@imageCrop',
+            disabled: '@disableImageResize'
+        },
+        {
+            action: 'saveImage',
+            disabled: '@disableImageResize'
+        },
+        {
+            action: 'resizeImage',
+            maxWidth: '@previewMaxWidth',
+            maxHeight: '@previewMaxHeight',
+            minWidth: '@previewMinWidth',
+            minHeight: '@previewMinHeight',
+            crop: '@previewCrop',
+            canvas: '@previewAsCanvas',
+            disabled: '@disableImagePreview'
+        },
+        {
+            action: 'setImage',
+            // The name of the property the resized image
+            // is saved as on the associated file object:
+            name: 'preview',
+            disabled: '@disableImagePreview'
+        }
+    );
+
+    // The File Upload Resize plugin extends the fileupload widget
+    // with image resize functionality:
+    $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+
+        options: {
+            // The regular expression for the types of images to load:
+            // matched against the file type:
+            loadImageFileTypes: /^image\/(gif|jpeg|png)$/,
+            // The maximum file size of images to load:
+            loadImageMaxFileSize: 5000000, // 5MB
+            // The maximum width of resized images:
+            imageMaxWidth: 1920,
+            // The maximum height of resized images:
+            imageMaxHeight: 1080,
+            // Define if resized images should be cropped or only scaled:
+            imageCrop: false,
+            // Disable the resize image functionality by default:
+            disableImageResize: true,
+            // The maximum width of the preview images:
+            previewMaxWidth: 80,
+            // The maximum height of the preview images:
+            previewMaxHeight: 80,
+            // Define if preview images should be cropped or only scaled:
+            previewCrop: false,
+            // Define if preview images should be resized as canvas elements:
+            previewAsCanvas: true
+        },
+
+        processActions: {
+
+            // Loads the image given via data.files and data.index
+            // as img element if the browser supports canvas.
+            // Accepts the options fileTypes (regular expression)
+            // and maxFileSize (integer) to limit the files to load:
+            loadImage: function (data, options) {
+                if (options.disabled) {
+                    return data;
+                }
+                var that = this,
+                    file = data.files[data.index],
+                    dfd = $.Deferred();
+                if (($.type(options.maxFileSize) === 'number' &&
+                            file.size > options.maxFileSize) ||
+                        (options.fileTypes &&
+                            !options.fileTypes.test(file.type)) ||
+                        !loadImage(
+                            file,
+                            function (img) {
+                                if (!img.src) {
+                                    return dfd.rejectWith(that, [data]);
+                                }
+                                data.img = img;
+                                dfd.resolveWith(that, [data]);
+                            },
+                            options
+                        )) {
+                    dfd.rejectWith(that, [data]);
+                }
+                return dfd.promise();
+            },
+
+            // Resizes the image given as data.canvas or data.img
+            // and updates data.canvas or data.img with the resized image.
+            // Accepts the options maxWidth, maxHeight, minWidth,
+            // minHeight, canvas and crop:
+            resizeImage: function (data, options) {
+                options = $.extend({canvas: true}, options);
+                var img = (options.canvas && data.canvas) || data.img,
+                    canvas;
+                if (img && !options.disabled) {
+                    canvas = loadImage.scale(img, options);
+                    if (canvas && (canvas.width !== img.width ||
+                            canvas.height !== img.height)) {
+                        data[canvas.getContext ? 'canvas' : 'img'] = canvas;
+                    }
+                }
+                return data;
+            },
+
+            // Saves the processed image given as data.canvas
+            // inplace at data.index of data.files:
+            saveImage: function (data, options) {
+                if (!data.canvas || options.disabled) {
+                    return data;
+                }
+                var that = this,
+                    file = data.files[data.index],
+                    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 if (data.canvas.toBlob) {
+                    data.canvas.toBlob(callback, file.type);
+                } else {
+                    return data;
+                }
+                return dfd.promise();
+            },
+
+            // Sets the resized version of the image as a property of the
+            // file object, must be called after "saveImage":
+            setImage: function (data, options) {
+                var img = data.canvas || data.img;
+                if (img && !options.disabled) {
+                    data.files[data.index][options.name] = img;
+                }
+                return data;
+            }
+
+        }
+
+    });
+
+}));
Index: admin/js/jsUpload/jquery.fileupload-ui.js
===================================================================
--- admin/js/jsUpload/jquery.fileupload-ui.js	(revision 1144)
+++ admin/js/jsUpload/jquery.fileupload-ui.js	(revision 1144)
@@ -0,0 +1,633 @@
+/*
+ * jQuery File Upload User Interface Plugin 8.2.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
+ */
+
+/*jslint nomen: true, unparam: true, regexp: true */
+/*global define, window, URL, webkitURL, FileReader */
+
+(function (factory) {
+    'use strict';
+    if (typeof define === 'function' && define.amd) {
+        // Register as an anonymous AMD module:
+        define([
+            'jquery',
+            'tmpl',
+            './jquery.fileupload-resize',
+            './jquery.fileupload-validate'
+        ], factory);
+    } else {
+        // Browser globals:
+        factory(
+            window.jQuery,
+            window.tmpl
+        );
+    }
+}(function ($, tmpl, loadImage) {
+    'use strict';
+
+    $.blueimp.fileupload.prototype._specialOptions.push(
+        'filesContainer',
+        'uploadTemplateId',
+        'downloadTemplateId'
+    );
+
+    // The UI version extends the file upload widget
+    // and adds complete user interface interaction:
+    $.widget('blueimp.fileupload', $.blueimp.fileupload, {
+
+        options: {
+            // By default, files added to the widget are uploaded as soon
+            // as the user clicks on the start buttons. To enable automatic
+            // uploads, set the following option to true:
+            autoUpload: false,
+            // The 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',
+
+            // Function returning the current number of files,
+            // used by the maxNumberOfFiles validation:
+            getNumberOfFiles: function () {
+                return this.filesContainer.children().length;
+            },
+
+            // Callback to retrieve the list of files from the server response:
+            getFilesFromResponse: function (data) {
+                if (data.result && $.isArray(data.result.files)) {
+                    return data.result.files;
+                }
+                return [];
+            },
+
+            // The add callback is invoked as soon as files are added to the fileupload
+            // widget (via file input selection, drag & drop or add API call).
+            // See the basic file upload widget for more information:
+            add: function (e, data) {
+                var $this = $(this),
+                    that = $this.data('blueimp-fileupload') ||
+                        $this.data('fileupload'),
+                    options = that.options,
+                    files = data.files;
+                data.process(function () {
+                    return $this.fileupload('process', data);
+                }).always(function () {
+                    data.context = that._renderUpload(files).data('data', data);
+                    that._renderPreviews(data);
+                    options.filesContainer[
+                        options.prependFiles ? 'prepend' : 'append'
+                    ](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.files.error) {
+                                data.submit();
+                            }
+                        }
+                    );
+                });
+            },
+            // Callback for the start of each file upload request:
+            send: function (e, data) {
+                var that = $(this).data('blueimp-fileupload') ||
+                        $(this).data('fileupload');
+                if (data.context && data.dataType &&
+                        data.dataType.substr(0, 6) === 'iframe') {
+                    // Iframe Transport does not support progress events.
+                    // In lack of an indeterminate progress bar, we set
+                    // the progress to 100%, showing the full animated bar:
+                    data.context
+                        .find('.progress').addClass(
+                            !$.support.transition && 'progress-animated'
+                        )
+                        .attr('aria-valuenow', 100)
+                        .find('.bar').css(
+                            'width',
+                            '100%'
+                        );
+                }
+                return that._trigger('sent', e, data);
+            },
+            // Callback for successful uploads:
+            done: function (e, data) {
+                var that = $(this).data('blueimp-fileupload') ||
+                        $(this).data('fileupload'),
+                    getFilesFromResponse = data.getFilesFromResponse ||
+                        that.options.getFilesFromResponse,
+                    files = getFilesFromResponse(data),
+                    template,
+                    deferred;
+                if (data.context) {
+                    data.context.each(function (index) {
+                        var file = files[index] ||
+                                {error: 'Empty file upload result'},
+                            deferred = that._addFinishedDeferreds();
+                        that._transition($(this)).done(
+                            function () {
+                                var node = $(this);
+                                template = that._renderDownload([file])
+                                    .replaceAll(node);
+                                that._forceReflow(template);
+                                that._transition(template).done(
+                                    function () {
+                                        data.context = $(this);
+                                        that._trigger('completed', e, data);
+                                        that._trigger('finished', e, data);
+                                        deferred.resolve();
+                                    }
+                                );
+                            }
+                        );
+                    });
+                } else {
+                    template = that._renderDownload(files)
+                        .appendTo(that.options.filesContainer);
+                    that._forceReflow(template);
+                    deferred = that._addFinishedDeferreds();
+                    that._transition(template).done(
+                        function () {
+                            data.context = $(this);
+                            that._trigger('completed', e, data);
+                            that._trigger('finished', e, data);
+                            deferred.resolve();
+                        }
+                    );
+                }
+            },
+            // Callback for failed (abort or error) uploads:
+            fail: function (e, data) {
+                var that = $(this).data('blueimp-fileupload') ||
+                        $(this).data('fileupload'),
+                    template,
+                    deferred;
+                if (data.context) {
+                    data.context.each(function (index) {
+                        if (data.errorThrown !== 'abort') {
+                            var file = data.files[index];
+                            file.error = file.error || data.errorThrown ||
+                                true;
+                            deferred = that._addFinishedDeferreds();
+                            that._transition($(this)).done(
+                                function () {
+                                    var node = $(this);
+                                    template = that._renderDownload([file])
+                                        .replaceAll(node);
+                                    that._forceReflow(template);
+                                    that._transition(template).done(
+                                        function () {
+                                            data.context = $(this);
+                                            that._trigger('failed', e, data);
+                                            that._trigger('finished', e, data);
+                                            deferred.resolve();
+                                        }
+                                    );
+                                }
+                            );
+                        } else {
+                            deferred = that._addFinishedDeferreds();
+                            that._transition($(this)).done(
+                                function () {
+                                    $(this).remove();
+                                    that._trigger('failed', e, data);
+                                    that._trigger('finished', e, data);
+                                    deferred.resolve();
+                                }
+                            );
+                        }
+                    });
+                } else if (data.errorThrown !== 'abort') {
+                    data.context = that._renderUpload(data.files)
+                        .appendTo(that.options.filesContainer)
+                        .data('data', data);
+                    that._forceReflow(data.context);
+                    deferred = that._addFinishedDeferreds();
+                    that._transition(data.context).done(
+                        function () {
+                            data.context = $(this);
+                            that._trigger('failed', e, data);
+                            that._trigger('finished', e, data);
+                            deferred.resolve();
+                        }
+                    );
+                } else {
+                    that._trigger('failed', e, data);
+                    that._trigger('finished', e, data);
+                    that._addFinishedDeferreds().resolve();
+                }
+            },
+            // Callback for upload progress events:
+            progress: function (e, data) {
+                if (data.context) {
+                    var progress = Math.floor(data.loaded / data.total * 100);
+                    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 = Math.floor(data.loaded / data.total * 100),
+                    globalProgressNode = $this.find('.fileupload-progress'),
+                    extendedProgressNode = globalProgressNode
+                        .find('.progress-extended');
+                if (extendedProgressNode.length) {
+                    extendedProgressNode.html(
+                        ($this.data('blueimp-fileupload') || $this.data('fileupload'))
+                            ._renderExtendedProgress(data)
+                    );
+                }
+                globalProgressNode
+                    .find('.progress')
+                    .attr('aria-valuenow', progress)
+                    .find('.bar').css(
+                        'width',
+                        progress + '%'
+                    );
+            },
+            // Callback for uploads start, equivalent to the global ajaxStart event:
+            start: function (e) {
+                var that = $(this).data('blueimp-fileupload') ||
+                        $(this).data('fileupload');
+                that._resetFinishedDeferreds();
+                that._transition($(this).find('.fileupload-progress')).done(
+                    function () {
+                        that._trigger('started', e);
+                    }
+                );
+            },
+            // Callback for uploads stop, equivalent to the global ajaxStop event:
+            stop: function (e) {
+                var that = $(this).data('blueimp-fileupload') ||
+                        $(this).data('fileupload'),
+                    deferred = that._addFinishedDeferreds();
+                $.when.apply($, that._getFinishedDeferreds())
+                    .done(function () {
+                        that._trigger('stopped', e);
+                    });
+                that._transition($(this).find('.fileupload-progress')).done(
+                    function () {
+                        $(this).find('.progress')
+                            .attr('aria-valuenow', '0')
+                            .find('.bar').css('width', '0%');
+                        $(this).find('.progress-extended').html('&nbsp;');
+                        deferred.resolve();
+                    }
+                );
+            },
+            processstart: function () {
+                $(this).addClass('fileupload-processing');
+            },
+            processstop: function () {
+                $(this).removeClass('fileupload-processing');
+            },
+            // Callback for file deletion:
+            destroy: function (e, data) {
+                var that = $(this).data('blueimp-fileupload') ||
+                        $(this).data('fileupload');
+                if (data.url) {
+                    $.ajax(data).done(function () {
+                        that._transition(data.context).done(
+                            function () {
+                                $(this).remove();
+                                that._trigger('destroyed', e, data);
+                            }
+                        );
+                    });
+                }
+            }
+        },
+
+        _resetFinishedDeferreds: function () {
+            this._finishedUploads = [];
+        },
+
+        _addFinishedDeferreds: function (deferred) {
+            if (!deferred) {
+                deferred = $.Deferred();
+            }
+            this._finishedUploads.push(deferred);
+            return deferred;
+        },
+
+        _getFinishedDeferreds: function () {
+            return this._finishedUploads;
+        },
+
+        // Link handler, that allows to download files
+        // by drag & drop of the links to the desktop:
+        _enableDragToDesktop: function () {
+            var link = $(this),
+                url = link.prop('href'),
+                name = link.prop('download'),
+                type = 'application/octet-stream';
+            link.bind('dragstart', function (e) {
+                try {
+                    e.originalEvent.dataTransfer.setData(
+                        'DownloadURL',
+                        [type, name, url].join(':')
+                    );
+                } catch (ignore) {}
+            });
+        },
+
+        _formatFileSize: function (bytes) {
+            if (typeof bytes !== 'number') {
+                return '';
+            }
+            if (bytes >= 1000000000) {
+                return (bytes / 1000000000).toFixed(2) + ' GB';
+            }
+            if (bytes >= 1000000) {
+                return (bytes / 1000000).toFixed(2) + ' MB';
+            }
+            return (bytes / 1000).toFixed(2) + ' KB';
+        },
+
+        _formatBitrate: function (bits) {
+            if (typeof bits !== 'number') {
+                return '';
+            }
+            if (bits >= 1000000000) {
+                return (bits / 1000000000).toFixed(2) + ' Gbit/s';
+            }
+            if (bits >= 1000000) {
+                return (bits / 1000000).toFixed(2) + ' Mbit/s';
+            }
+            if (bits >= 1000) {
+                return (bits / 1000).toFixed(2) + ' kbit/s';
+            }
+            return bits.toFixed(2) + ' bit/s';
+        },
+
+        _formatTime: function (seconds) {
+            var date = new Date(seconds * 1000),
+                days = Math.floor(seconds / 86400);
+            days = days ? days + 'd ' : '';
+            return days +
+                ('0' + date.getUTCHours()).slice(-2) + ':' +
+                ('0' + date.getUTCMinutes()).slice(-2) + ':' +
+                ('0' + date.getUTCSeconds()).slice(-2);
+        },
+
+        _formatPercentage: function (floatValue) {
+            return (floatValue * 100).toFixed(2) + ' %';
+        },
+
+        _renderExtendedProgress: function (data) {
+            return this._formatBitrate(data.bitrate) + ' | ' +
+                this._formatTime(
+                    (data.total - data.loaded) * 8 / data.bitrate
+                ) + ' | ' +
+                this._formatPercentage(
+                    data.loaded / data.total
+                ) + ' | ' +
+                this._formatFileSize(data.loaded) + ' / ' +
+                this._formatFileSize(data.total);
+        },
+
+        _renderTemplate: function (func, files) {
+            if (!func) {
+                return $();
+            }
+            var result = func({
+                files: files,
+                formatFileSize: this._formatFileSize,
+                options: this.options
+            });
+            if (result instanceof $) {
+                return result;
+            }
+            return $(this.options.templatesContainer).html(result).children();
+        },
+
+        _renderPreviews: function (data) {
+            data.context.find('.preview').each(function (index, elm) {
+                $(elm).append(data.files[index].preview);
+            });
+        },
+
+        _renderUpload: function (files) {
+            return this._renderTemplate(
+                this.options.uploadTemplate,
+                files
+            );
+        },
+
+        _renderDownload: function (files) {
+            return this._renderTemplate(
+                this.options.downloadTemplate,
+                files
+            ).find('a[download]').each(this._enableDragToDesktop).end();
+        },
+
+        _startHandler: function (e) {
+            e.preventDefault();
+            var button = $(e.currentTarget),
+                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 = $(e.currentTarget).closest('.template-upload'),
+                data = template.data('data') || {};
+            if (!data.jqXHR) {
+                data.errorThrown = 'abort';
+                this._trigger('fail', e, data);
+            } else {
+                data.jqXHR.abort();
+            }
+        },
+
+        _deleteHandler: function (e) {
+            e.preventDefault();
+            var button = $(e.currentTarget);
+            this._trigger('destroy', e, $.extend({
+                context: button.closest('.template-download'),
+                type: 'DELETE'
+            }, button.data()));
+        },
+
+        _forceReflow: function (node) {
+            return $.support.transition && node.length &&
+                node[0].offsetWidth;
+        },
+
+        _transition: function (node) {
+            var dfd = $.Deferred();
+            if ($.support.transition && node.hasClass('fade') && node.is(':visible')) {
+                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;
+            this._on(fileUploadButtonBar.find('.start'), {
+                click: function (e) {
+                    e.preventDefault();
+                    filesList.find('.start').click();
+                }
+            });
+            this._on(fileUploadButtonBar.find('.cancel'), {
+                click: function (e) {
+                    e.preventDefault();
+                    filesList.find('.cancel').click();
+                }
+            });
+            this._on(fileUploadButtonBar.find('.delete'), {
+                click: function (e) {
+                    e.preventDefault();
+                    filesList.find('.toggle:checked')
+                        .closest('.template-download')
+                        .find('.delete').click();
+                    fileUploadButtonBar.find('.toggle')
+                        .prop('checked', false);
+                }
+            });
+            this._on(fileUploadButtonBar.find('.toggle'), {
+                change: function (e) {
+                    filesList.find('.toggle').prop(
+                        'checked',
+                        $(e.currentTarget).is(':checked')
+                    );
+                }
+            });
+        },
+
+        _destroyButtonBarEventHandlers: function () {
+            this._off(
+                this.element.find('.fileupload-buttonbar')
+                    .find('.start, .cancel, .delete'),
+                'click'
+            );
+            this._off(
+                this.element.find('.fileupload-buttonbar .toggle'),
+                'change.'
+            );
+        },
+
+        _initEventHandlers: function () {
+            this._super();
+            this._on(this.options.filesContainer, {
+                'click .start': this._startHandler,
+                'click .cancel': this._cancelHandler,
+                'click .delete': this._deleteHandler
+            });
+            this._initButtonBarEventHandlers();
+        },
+
+        _destroyEventHandlers: function () {
+            this._destroyButtonBarEventHandlers();
+            this._off(this.options.filesContainer, 'click');
+            this._super();
+        },
+
+        _enableFileInputButton: function () {
+            this.element.find('.fileinput-button input')
+                .prop('disabled', false)
+                .parent().removeClass('disabled');
+        },
+
+        _disableFileInputButton: function () {
+            this.element.find('.fileinput-button input')
+                .prop('disabled', true)
+                .parent().addClass('disabled');
+        },
+
+        _initTemplates: function () {
+            var options = this.options;
+            options.templatesContainer = this.document[0].createElement(
+                options.filesContainer.prop('nodeName')
+            );
+            if (tmpl) {
+                if (options.uploadTemplateId) {
+                    options.uploadTemplate = tmpl(options.uploadTemplateId);
+                }
+                if (options.downloadTemplateId) {
+                    options.downloadTemplate = tmpl(options.downloadTemplateId);
+                }
+            }
+        },
+
+        _initFilesContainer: function () {
+            var options = this.options;
+            if (options.filesContainer === undefined) {
+                options.filesContainer = this.element.find('.files');
+            } else if (!(options.filesContainer instanceof $)) {
+                options.filesContainer = $(options.filesContainer);
+            }
+        },
+
+        _initSpecialOptions: function () {
+            this._super();
+            this._initFilesContainer();
+            this._initTemplates();
+        },
+
+        _create: function () {
+            this._super();
+            this._resetFinishedDeferreds();
+        },
+
+        enable: function () {
+            var wasDisabled = false;
+            if (this.options.disabled) {
+                wasDisabled = true;
+            }
+            this._super();
+            if (wasDisabled) {
+                this.element.find('input, button').prop('disabled', false);
+                this._enableFileInputButton();
+            }
+        },
+
+        disable: function () {
+            if (!this.options.disabled) {
+                this.element.find('input, button').prop('disabled', true);
+                this._disableFileInputButton();
+            }
+            this._super();
+        }
+
+    });
+
+}));
Index: admin/js/jsUpload/jquery.fileupload.js
===================================================================
--- admin/js/jsUpload/jquery.fileupload.js	(revision 1144)
+++ admin/js/jsUpload/jquery.fileupload.js	(revision 1144)
@@ -0,0 +1,1301 @@
+/*
+ * jQuery File Upload Plugin 5.31.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
+ */
+
+/*jslint nomen: true, unparam: true, regexp: true */
+/*global define, window, document, File, 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 drop target element(s), by the default the complete document.
+            // Set to null to disable drag & drop support:
+            dropZone: $(document),
+            // The paste target element(s), by the default the complete document.
+            // Set to null to disable paste support:
+            pasteZone: $(document),
+            // The file input field(s), that are listened to for change events.
+            // If undefined, it is set to the file input fields inside
+            // of the widget element on plugin initialization.
+            // Set to null to disable the change listener.
+            fileInput: undefined,
+            // By default, the file input field is replaced with a clone after
+            // each input field change event. This is required for iframe transport
+            // queues and allows change events to be fired for the same file
+            // selection, but can be disabled by setting the following option to false:
+            replaceFileInput: true,
+            // The parameter name for the file form data (the request argument name).
+            // If undefined or empty, the name property of the file input field is
+            // used, or "files[]" if the file input name property is also empty,
+            // can be a string or an array of strings:
+            paramName: undefined,
+            // By default, each file of a selection is uploaded using an individual
+            // request for XHR type uploads. Set to false to upload file
+            // selections in one request each:
+            singleFileUploads: true,
+            // To limit the number of files uploaded with one XHR request,
+            // set the following option to an integer greater than 0:
+            limitMultiFileUploads: undefined,
+            // Set the following option to true to issue all file upload requests
+            // in a sequential order:
+            sequentialUploads: false,
+            // To limit the number of concurrent uploads,
+            // set the following option to an integer greater than 0:
+            limitConcurrentUploads: undefined,
+            // Set the following option to true to force iframe transport uploads:
+            forceIframeTransport: false,
+            // Set the following option to the location of a redirect url on the
+            // origin server, for cross-domain iframe transport uploads:
+            redirect: undefined,
+            // The parameter name for the redirect url, sent as part of the form
+            // data and set to 'redirect' if this option is empty:
+            redirectParamName: undefined,
+            // Set the following option to the location of a postMessage window,
+            // to enable postMessage transport uploads:
+            postMessage: undefined,
+            // By default, XHR file uploads are sent as multipart/form-data.
+            // The iframe transport is always using multipart/form-data.
+            // Set to false to enable non-multipart XHR uploads:
+            multipart: true,
+            // To upload large files in smaller chunks, set the following option
+            // to a preferred maximum chunk size. If set to 0, null or undefined,
+            // or the browser does not support the required Blob API, files will
+            // be uploaded as a whole.
+            maxChunkSize: undefined,
+            // When a non-multipart upload or a chunked multipart upload has been
+            // aborted, this option can be used to resume the upload by setting
+            // it to the size of the already uploaded bytes. This option is most
+            // useful when modifying the options object inside of the "add" or
+            // "send" callbacks, as the options are cloned for each file upload.
+            uploadedBytes: undefined,
+            // By default, failed (abort or error) file uploads are removed from the
+            // global progress calculation. Set the following option to false to
+            // prevent recalculating the global progress data:
+            recalculateProgress: true,
+            // Interval in milliseconds to calculate and trigger progress events:
+            progressInterval: 100,
+            // Interval in milliseconds to calculate progress bitrate:
+            bitrateInterval: 500,
+            // By default, uploads are started automatically when adding files:
+            autoUpload: true,
+
+            // Error and info messages:
+            messages: {
+                uploadedBytes: 'Uploaded bytes exceed file size'
+            },
+
+            // Translation function, gets the message key to be translated
+            // and an object with context specific data as arguments:
+            i18n: function (message, context) {
+                message = this.messages[message] || message.toString();
+                if (context) {
+                    $.each(context, function (key, value) {
+                        message = message.replace('{' + key + '}', value);
+                    });
+                }
+                return message;
+            },
+
+            // Additional form data to be sent along with the file uploads can be set
+            // using this option, which accepts an array of objects with name and
+            // value properties, a function returning such an array, a FormData
+            // object (for XHR file uploads), or a simple object.
+            // The form of the first fileInput is given as parameter to the function:
+            formData: function (form) {
+                return form.serializeArray();
+            },
+
+            // The add callback is invoked as soon as files are added to the fileupload
+            // widget (via file input selection, drag & drop, paste or add API call).
+            // If the singleFileUploads option is enabled, this callback will be
+            // called once for each file in the selection for XHR file 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) {
+                if (data.autoUpload || (data.autoUpload !== false &&
+                        $(this).fileupload('option', 'autoUpload'))) {
+                    data.process().done(function () {
+                        data.submit();
+                    });
+                }
+            },
+
+            // Other callbacks:
+
+            // Callback for the submit event of each file upload:
+            // submit: function (e, data) {}, // .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(s):
+            // change: function (e, data) {}, // .bind('fileuploadchange', func);
+
+            // Callback for paste events to the pasteZone(s):
+            // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
+
+            // Callback for drop events of the dropZone(s):
+            // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
+
+            // Callback for dragover events of the dropZone(s):
+            // dragover: function (e) {}, // .bind('fileuploaddragover', func);
+
+            // Callback for the start of each chunk upload request:
+            // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
+
+            // Callback for successful chunk uploads:
+            // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
+
+            // Callback for failed (abort or error) chunk uploads:
+            // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
+
+            // Callback for completed (success, abort or error) chunk upload requests:
+            // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
+
+            // The plugin options are used as settings object for the ajax calls.
+            // The following are jQuery ajax settings required for the file uploads:
+            processData: false,
+            contentType: false,
+            cache: false
+        },
+
+        // A list of options that require reinitializing event listeners and/or
+        // special initialization code:
+        _specialOptions: [
+            'fileInput',
+            'dropZone',
+            'pasteZone',
+            'multipart',
+            'forceIframeTransport'
+        ],
+
+        _BitrateTimer: function () {
+            this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
+            this.loaded = 0;
+            this.bitrate = 0;
+            this.getBitrate = function (now, loaded, interval) {
+                var timeDiff = now - this.timestamp;
+                if (!this.bitrate || !interval || timeDiff > interval) {
+                    this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
+                    this.loaded = loaded;
+                    this.timestamp = now;
+                }
+                return this.bitrate;
+            };
+        },
+
+        _isXHRUpload: function (options) {
+            return !options.forceIframeTransport &&
+                ((!options.multipart && $.support.xhrFileUpload) ||
+                $.support.xhrFormDataFileUpload);
+        },
+
+        _getFormData: function (options) {
+            var formData;
+            if (typeof options.formData === 'function') {
+                return options.formData(options.form);
+            }
+            if ($.isArray(options.formData)) {
+                return options.formData;
+            }
+            if ($.type(options.formData) === 'object') {
+                formData = [];
+                $.each(options.formData, function (name, value) {
+                    formData.push({name: name, value: value});
+                });
+                return formData;
+            }
+            return [];
+        },
+
+        _getTotal: function (files) {
+            var total = 0;
+            $.each(files, function (index, file) {
+                total += file.size || 1;
+            });
+            return total;
+        },
+
+        _initProgressObject: function (obj) {
+            var progress = {
+                loaded: 0,
+                total: 0,
+                bitrate: 0
+            };
+            if (obj._progress) {
+                $.extend(obj._progress, progress);
+            } else {
+                obj._progress = progress;
+            }
+        },
+
+        _initResponseObject: function (obj) {
+            var prop;
+            if (obj._response) {
+                for (prop in obj._response) {
+                    if (obj._response.hasOwnProperty(prop)) {
+                        delete obj._response[prop];
+                    }
+                }
+            } else {
+                obj._response = {};
+            }
+        },
+
+        _onProgress: function (e, data) {
+            if (e.lengthComputable) {
+                var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
+                    loaded;
+                if (data._time && data.progressInterval &&
+                        (now - data._time < data.progressInterval) &&
+                        e.loaded !== e.total) {
+                    return;
+                }
+                data._time = now;
+                loaded = Math.floor(
+                    e.loaded / e.total * (data.chunkSize || data._progress.total)
+                ) + (data.uploadedBytes || 0);
+                // Add the difference from the previously loaded state
+                // to the global loaded counter:
+                this._progress.loaded += (loaded - data._progress.loaded);
+                this._progress.bitrate = this._bitrateTimer.getBitrate(
+                    now,
+                    this._progress.loaded,
+                    data.bitrateInterval
+                );
+                data._progress.loaded = data.loaded = loaded;
+                data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
+                    now,
+                    loaded,
+                    data.bitrateInterval
+                );
+                // Trigger a custom progress event with a total data property set
+                // to the file size(s) of the current upload and a loaded data
+                // property calculated accordingly:
+                this._trigger('progress', e, data);
+                // Trigger a global progress event for all current file uploads,
+                // including ajax calls queued for sequential file uploads:
+                this._trigger('progressall', e, this._progress);
+            }
+        },
+
+        _initProgressListener: function (options) {
+            var that = this,
+                xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
+            // Accesss to the native XHR object is required to add event listeners
+            // for the upload progress event:
+            if (xhr.upload) {
+                $(xhr.upload).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;
+                };
+            }
+        },
+
+        _isInstanceOf: function (type, obj) {
+            // Cross-frame instanceof check
+            return Object.prototype.toString.call(obj) === '[object ' + type + ']';
+        },
+
+        _initXHRData: function (options) {
+            var that = this,
+                formData,
+                file = options.files[0],
+                // Ignore non-multipart setting if not supported:
+                multipart = options.multipart || !$.support.xhrFileUpload,
+                paramName = options.paramName[0];
+            options.headers = options.headers || {};
+            if (options.contentRange) {
+                options.headers['Content-Range'] = options.contentRange;
+            }
+            if (!multipart) {
+                options.headers['Content-Disposition'] = 'attachment; filename="' +
+                    encodeURI(file.name) + '"';
+                options.contentType = file.type;
+                options.data = options.blob || file;
+            } else if ($.support.xhrFormDataFileUpload) {
+                if (options.postMessage) {
+                    // window.postMessage does not allow sending FormData
+                    // objects, so we just add the File/Blob objects to
+                    // the formData array and let the postMessage window
+                    // create the FormData object out of this array:
+                    formData = this._getFormData(options);
+                    if (options.blob) {
+                        formData.push({
+                            name: paramName,
+                            value: options.blob
+                        });
+                    } else {
+                        $.each(options.files, function (index, file) {
+                            formData.push({
+                                name: options.paramName[index] || paramName,
+                                value: file
+                            });
+                        });
+                    }
+                } else {
+                    if (that._isInstanceOf('FormData', options.formData)) {
+                        formData = options.formData;
+                    } else {
+                        formData = new FormData();
+                        $.each(this._getFormData(options), function (index, field) {
+                            formData.append(field.name, field.value);
+                        });
+                    }
+                    if (options.blob) {
+                        options.headers['Content-Disposition'] = 'attachment; filename="' +
+                            encodeURI(file.name) + '"';
+                        formData.append(paramName, options.blob, file.name);
+                    } else {
+                        $.each(options.files, function (index, file) {
+                            // This check allows the tests to run with
+                            // dummy objects:
+                            if (that._isInstanceOf('File', file) ||
+                                    that._isInstanceOf('Blob', file)) {
+                                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);
+            }
+        },
+
+        _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'));
+                // If the given file input doesn't have an associated form,
+                // use the default widget file input's form:
+                if (!options.form.length) {
+                    options.form = $(this.options.fileInput.prop('form'));
+                }
+            }
+            options.paramName = this._getParamName(options);
+            if (!options.url) {
+                options.url = options.form.prop('action') || location.href;
+            }
+            // The HTTP request method must be "POST" or "PUT":
+            options.type = (options.type || options.form.prop('method') || '')
+                .toUpperCase();
+            if (options.type !== 'POST' && options.type !== 'PUT' &&
+                    options.type !== 'PATCH') {
+                options.type = 'POST';
+            }
+            if (!options.formAcceptCharset) {
+                options.formAcceptCharset = options.form.attr('accept-charset');
+            }
+        },
+
+        _getAJAXSettings: function (data) {
+            var options = $.extend({}, this.options, data);
+            this._initFormSettings(options);
+            this._initDataSettings(options);
+            return options;
+        },
+
+        // jQuery 1.6 doesn't provide .state(),
+        // while jQuery 1.8+ removed .isRejected() and .isResolved():
+        _getDeferredState: function (deferred) {
+            if (deferred.state) {
+                return deferred.state();
+            }
+            if (deferred.isResolved()) {
+                return 'resolved';
+            }
+            if (deferred.isRejected()) {
+                return 'rejected';
+            }
+            return 'pending';
+        },
+
+        // 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);
+        },
+
+        // Adds convenience methods to the data callback argument:
+        _addConvenienceMethods: function (e, data) {
+            var that = this,
+                getPromise = function (data) {
+                    return $.Deferred().resolveWith(that, [data]).promise();
+                };
+            data.process = function (resolveFunc, rejectFunc) {
+                if (resolveFunc || rejectFunc) {
+                    data._processQueue = this._processQueue =
+                        (this._processQueue || getPromise(this))
+                            .pipe(resolveFunc, rejectFunc);
+                }
+                return this._processQueue || getPromise(this);
+            };
+            data.submit = function () {
+                if (this.state() !== 'pending') {
+                    data.jqXHR = this.jqXHR =
+                        (that._trigger('submit', e, this) !== false) &&
+                        that._onSend(e, this);
+                }
+                return this.jqXHR || that._getXHRPromise();
+            };
+            data.abort = function () {
+                if (this.jqXHR) {
+                    return this.jqXHR.abort();
+                }
+                return that._getXHRPromise();
+            };
+            data.state = function () {
+                if (this.jqXHR) {
+                    return that._getDeferredState(this.jqXHR);
+                }
+                if (this._processQueue) {
+                    return that._getDeferredState(this._processQueue);
+                }
+            };
+            data.progress = function () {
+                return this._progress;
+            };
+            data.response = function () {
+                return this._response;
+            };
+        },
+
+        // Parses the Range header from the server response
+        // and returns the uploaded bytes:
+        _getUploadedBytes: function (jqXHR) {
+            var range = jqXHR.getResponseHeader('Range'),
+                parts = range && range.split('-'),
+                upperBytesPos = parts && parts.length > 1 &&
+                    parseInt(parts[1], 10);
+            return upperBytesPos && upperBytesPos + 1;
+        },
+
+        // Uploads a file in multiple, sequential requests
+        // by splitting the file up in multiple blob chunks.
+        // If the second parameter is true, only tests if the file
+        // should be uploaded in chunks, but does not invoke any
+        // upload requests:
+        _chunkedUpload: function (options, testOnly) {
+            var that = this,
+                file = options.files[0],
+                fs = file.size,
+                ub = options.uploadedBytes = options.uploadedBytes || 0,
+                mcs = options.maxChunkSize || fs,
+                slice = file.slice || file.webkitSlice || file.mozSlice,
+                dfd = $.Deferred(),
+                promise = dfd.promise(),
+                jqXHR,
+                upload;
+            if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
+                    options.data) {
+                return false;
+            }
+            if (testOnly) {
+                return true;
+            }
+            if (ub >= fs) {
+                file.error = options.i18n('uploadedBytes');
+                return this._getXHRPromise(
+                    false,
+                    options.context,
+                    [null, 'error', file.error]
+                );
+            }
+            // The chunk upload method:
+            upload = function () {
+                // Clone the options object for each chunk upload:
+                var o = $.extend({}, options),
+                    currentLoaded = o._progress.loaded;
+                o.blob = slice.call(
+                    file,
+                    ub,
+                    ub + mcs,
+                    file.type
+                );
+                // Store the current chunk size, as the blob itself
+                // will be dereferenced after data processing:
+                o.chunkSize = o.blob.size;
+                // Expose the chunk bytes position range:
+                o.contentRange = 'bytes ' + ub + '-' +
+                    (ub + o.chunkSize - 1) + '/' + fs;
+                // Process the upload data (the blob and potential form data):
+                that._initXHRData(o);
+                // Add progress listeners for this chunk upload:
+                that._initProgressListener(o);
+                jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
+                        that._getXHRPromise(false, o.context))
+                    .done(function (result, textStatus, jqXHR) {
+                        ub = that._getUploadedBytes(jqXHR) ||
+                            (ub + o.chunkSize);
+                        // Create a progress event if no final progress event
+                        // with loaded equaling total has been triggered
+                        // for this chunk:
+                        if (currentLoaded + o.chunkSize - o._progress.loaded) {
+                            that._onProgress($.Event('progress', {
+                                lengthComputable: true,
+                                loaded: ub - o.uploadedBytes,
+                                total: ub - o.uploadedBytes
+                            }), o);
+                        }
+                        options.uploadedBytes = o.uploadedBytes = ub;
+                        o.result = result;
+                        o.textStatus = textStatus;
+                        o.jqXHR = jqXHR;
+                        that._trigger('chunkdone', null, o);
+                        that._trigger('chunkalways', null, o);
+                        if (ub < fs) {
+                            // File upload not yet complete,
+                            // continue with the next chunk:
+                            upload();
+                        } else {
+                            dfd.resolveWith(
+                                o.context,
+                                [result, textStatus, jqXHR]
+                            );
+                        }
+                    })
+                    .fail(function (jqXHR, textStatus, errorThrown) {
+                        o.jqXHR = jqXHR;
+                        o.textStatus = textStatus;
+                        o.errorThrown = errorThrown;
+                        that._trigger('chunkfail', null, o);
+                        that._trigger('chunkalways', null, o);
+                        dfd.rejectWith(
+                            o.context,
+                            [jqXHR, textStatus, errorThrown]
+                        );
+                    });
+            };
+            this._enhancePromise(promise);
+            promise.abort = function () {
+                return jqXHR.abort();
+            };
+            upload();
+            return promise;
+        },
+
+        _beforeSend: function (e, data) {
+            if (this._active === 0) {
+                // the start callback is triggered when an upload starts
+                // and no other uploads are currently running,
+                // equivalent to the global ajaxStart event:
+                this._trigger('start');
+                // Set timer for global bitrate progress calculation:
+                this._bitrateTimer = new this._BitrateTimer();
+                // Reset the global progress values:
+                this._progress.loaded = this._progress.total = 0;
+                this._progress.bitrate = 0;
+            }
+            // Make sure the container objects for the .response() and
+            // .progress() methods on the data object are available
+            // and reset to their initial state:
+            this._initResponseObject(data);
+            this._initProgressObject(data);
+            data._progress.loaded = data.loaded = data.uploadedBytes || 0;
+            data._progress.total = data.total = this._getTotal(data.files) || 1;
+            data._progress.bitrate = data.bitrate = 0;
+            this._active += 1;
+            // Initialize the global progress values:
+            this._progress.loaded += data.loaded;
+            this._progress.total += data.total;
+        },
+
+        _onDone: function (result, textStatus, jqXHR, options) {
+            var total = options._progress.total,
+                response = options._response;
+            if (options._progress.loaded < total) {
+                // Create a progress event if no final progress event
+                // with loaded equaling total has been triggered:
+                this._onProgress($.Event('progress', {
+                    lengthComputable: true,
+                    loaded: total,
+                    total: total
+                }), options);
+            }
+            response.result = options.result = result;
+            response.textStatus = options.textStatus = textStatus;
+            response.jqXHR = options.jqXHR = jqXHR;
+            this._trigger('done', null, options);
+        },
+
+        _onFail: function (jqXHR, textStatus, errorThrown, options) {
+            var response = options._response;
+            if (options.recalculateProgress) {
+                // Remove the failed (error or abort) file upload from
+                // the global progress calculation:
+                this._progress.loaded -= options._progress.loaded;
+                this._progress.total -= options._progress.total;
+            }
+            response.jqXHR = options.jqXHR = jqXHR;
+            response.textStatus = options.textStatus = textStatus;
+            response.errorThrown = options.errorThrown = errorThrown;
+            this._trigger('fail', null, options);
+        },
+
+        _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
+            // jqXHRorResult, textStatus and jqXHRorError are added to the
+            // options object via done and fail callbacks
+            this._trigger('always', null, options);
+        },
+
+        _onSend: function (e, data) {
+            if (!data.submit) {
+                this._addConvenienceMethods(e, data);
+            }
+            var that = this,
+                jqXHR,
+                aborted,
+                slot,
+                pipe,
+                options = that._getAJAXSettings(data),
+                send = function () {
+                    that._sending += 1;
+                    // Set timer for bitrate progress calculation:
+                    options._bitrateTimer = new that._BitrateTimer();
+                    jqXHR = jqXHR || (
+                        ((aborted || that._trigger('send', e, options) === false) &&
+                        that._getXHRPromise(false, options.context, aborted)) ||
+                        that._chunkedUpload(options) || $.ajax(options)
+                    ).done(function (result, textStatus, jqXHR) {
+                        that._onDone(result, textStatus, jqXHR, options);
+                    }).fail(function (jqXHR, textStatus, errorThrown) {
+                        that._onFail(jqXHR, textStatus, errorThrown, options);
+                    }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
+                        that._onAlways(
+                            jqXHRorResult,
+                            textStatus,
+                            jqXHRorError,
+                            options
+                        );
+                        that._sending -= 1;
+                        that._active -= 1;
+                        if (options.limitConcurrentUploads &&
+                                options.limitConcurrentUploads > that._sending) {
+                            // Start the next queued upload,
+                            // that has not been aborted:
+                            var nextSlot = that._slots.shift();
+                            while (nextSlot) {
+                                if (that._getDeferredState(nextSlot) === 'pending') {
+                                    nextSlot.resolve();
+                                    break;
+                                }
+                                nextSlot = that._slots.shift();
+                            }
+                        }
+                        if (that._active === 0) {
+                            // The stop callback is triggered when all uploads have
+                            // been completed, equivalent to the global ajaxStop event:
+                            that._trigger('stop');
+                        }
+                    });
+                    return jqXHR;
+                };
+            this._beforeSend(e, options);
+            if (this.options.sequentialUploads ||
+                    (this.options.limitConcurrentUploads &&
+                    this.options.limitConcurrentUploads <= this._sending)) {
+                if (this.options.limitConcurrentUploads > 1) {
+                    slot = $.Deferred();
+                    this._slots.push(slot);
+                    pipe = slot.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 () {
+                    aborted = [undefined, 'abort', 'abort'];
+                    if (!jqXHR) {
+                        if (slot) {
+                            slot.rejectWith(options.context, aborted);
+                        }
+                        return send();
+                    }
+                    return jqXHR.abort();
+                };
+                return this._enhancePromise(pipe);
+            }
+            return send();
+        },
+
+        _onAdd: function (e, data) {
+            var that = this,
+                result = true,
+                options = $.extend({}, this.options, data),
+                limit = options.limitMultiFileUploads,
+                paramName = this._getParamName(options),
+                paramNameSet,
+                paramNameSlice,
+                fileSet,
+                i;
+            if (!(options.singleFileUploads || limit) ||
+                    !this._isXHRUpload(options)) {
+                fileSet = [data.files];
+                paramNameSet = [paramName];
+            } else if (!options.singleFileUploads && limit) {
+                fileSet = [];
+                paramNameSet = [];
+                for (i = 0; i < data.files.length; i += limit) {
+                    fileSet.push(data.files.slice(i, i + limit));
+                    paramNameSlice = paramName.slice(i, i + limit);
+                    if (!paramNameSlice.length) {
+                        paramNameSlice = paramName;
+                    }
+                    paramNameSet.push(paramNameSlice);
+                }
+            } else {
+                paramNameSet = paramName;
+            }
+            data.originalFiles = data.files;
+            $.each(fileSet || data.files, function (index, element) {
+                var newData = $.extend({}, data);
+                newData.files = fileSet ? element : [element];
+                newData.paramName = paramNameSet[index];
+                that._initResponseObject(newData);
+                that._initProgressObject(newData);
+                that._addConvenienceMethods(e, newData);
+                result = that._trigger('add', e, newData);
+                return result;
+            });
+            return result;
+        },
+
+        _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
+            // elements set with the clone, which has been copied including
+            // event handlers:
+            this.options.fileInput = this.options.fileInput.map(function (i, el) {
+                if (el === input[0]) {
+                    return inputClone[0];
+                }
+                return el;
+            });
+            // If the widget has been initialized on the file input itself,
+            // override this.element with the file input clone:
+            if (input[0] === this.element[0]) {
+                this.element = inputClone;
+            }
+        },
+
+        _handleFileTreeEntry: function (entry, path) {
+            var that = this,
+                dfd = $.Deferred(),
+                errorHandler = function (e) {
+                    if (e && !e.entry) {
+                        e.entry = entry;
+                    }
+                    // Since $.when returns immediately if one
+                    // Deferred is rejected, we use resolve instead.
+                    // This allows valid files and invalid items
+                    // to be returned together in one set:
+                    dfd.resolve([e]);
+                },
+                dirReader;
+            path = path || '';
+            if (entry.isFile) {
+                if (entry._file) {
+                    // Workaround for Chrome bug #149735
+                    entry._file.relativePath = path;
+                    dfd.resolve(entry._file);
+                } else {
+                    entry.file(function (file) {
+                        file.relativePath = path;
+                        dfd.resolve(file);
+                    }, errorHandler);
+                }
+            } else if (entry.isDirectory) {
+                dirReader = entry.createReader();
+                dirReader.readEntries(function (entries) {
+                    that._handleFileTreeEntries(
+                        entries,
+                        path + entry.name + '/'
+                    ).done(function (files) {
+                        dfd.resolve(files);
+                    }).fail(errorHandler);
+                }, errorHandler);
+            } else {
+                // Return an empy list for file system items
+                // other than files or directories:
+                dfd.resolve([]);
+            }
+            return dfd.promise();
+        },
+
+        _handleFileTreeEntries: function (entries, path) {
+            var that = this;
+            return $.when.apply(
+                $,
+                $.map(entries, function (entry) {
+                    return that._handleFileTreeEntry(entry, path);
+                })
+            ).pipe(function () {
+                return Array.prototype.concat.apply(
+                    [],
+                    arguments
+                );
+            });
+        },
+
+        _getDroppedFiles: function (dataTransfer) {
+            dataTransfer = dataTransfer || {};
+            var items = dataTransfer.items;
+            if (items && items.length && (items[0].webkitGetAsEntry ||
+                    items[0].getAsEntry)) {
+                return this._handleFileTreeEntries(
+                    $.map(items, function (item) {
+                        var entry;
+                        if (item.webkitGetAsEntry) {
+                            entry = item.webkitGetAsEntry();
+                            if (entry) {
+                                // Workaround for Chrome bug #149735:
+                                entry._file = item.getAsFile();
+                            }
+                            return entry;
+                        }
+                        return item.getAsEntry();
+                    })
+                );
+            }
+            return $.Deferred().resolve(
+                $.makeArray(dataTransfer.files)
+            ).promise();
+        },
+
+        _getSingleFileInputFiles: function (fileInput) {
+            fileInput = $(fileInput);
+            var entries = fileInput.prop('webkitEntries') ||
+                    fileInput.prop('entries'),
+                files,
+                value;
+            if (entries && entries.length) {
+                return this._handleFileTreeEntries(entries);
+            }
+            files = $.makeArray(fileInput.prop('files'));
+            if (!files.length) {
+                value = fileInput.prop('value');
+                if (!value) {
+                    return $.Deferred().resolve([]).promise();
+                }
+                // If the files property is not available, the browser does not
+                // support the File API and we add a pseudo File object with
+                // the input value as name with path information removed:
+                files = [{name: value.replace(/^.*\\/, '')}];
+            } else if (files[0].name === undefined && files[0].fileName) {
+                // File normalization for Safari 4 and Firefox 3:
+                $.each(files, function (index, file) {
+                    file.name = file.fileName;
+                    file.size = file.fileSize;
+                });
+            }
+            return $.Deferred().resolve(files).promise();
+        },
+
+        _getFileInputFiles: function (fileInput) {
+            if (!(fileInput instanceof $) || fileInput.length === 1) {
+                return this._getSingleFileInputFiles(fileInput);
+            }
+            return $.when.apply(
+                $,
+                $.map(fileInput, this._getSingleFileInputFiles)
+            ).pipe(function () {
+                return Array.prototype.concat.apply(
+                    [],
+                    arguments
+                );
+            });
+        },
+
+        _onChange: function (e) {
+            var that = this,
+                data = {
+                    fileInput: $(e.target),
+                    form: $(e.target.form)
+                };
+            this._getFileInputFiles(data.fileInput).always(function (files) {
+                data.files = files;
+                if (that.options.replaceFileInput) {
+                    that._replaceFileInput(data.fileInput);
+                }
+                if (that._trigger('change', e, data) !== false) {
+                    that._onAdd(e, data);
+                }
+            });
+        },
+
+        _onPaste: function (e) {
+            var items = e.originalEvent && e.originalEvent.clipboardData &&
+                    e.originalEvent.clipboardData.items,
+                data = {files: []};
+            if (items && items.length) {
+                $.each(items, function (index, item) {
+                    var file = item.getAsFile && item.getAsFile();
+                    if (file) {
+                        data.files.push(file);
+                    }
+                });
+                if (this._trigger('paste', e, data) === false ||
+                        this._onAdd(e, data) === false) {
+                    return false;
+                }
+            }
+        },
+
+        _onDrop: function (e) {
+            var that = this,
+                dataTransfer = e.dataTransfer = e.originalEvent &&
+                    e.originalEvent.dataTransfer,
+                data = {};
+            if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
+                e.preventDefault();
+                this._getDroppedFiles(dataTransfer).always(function (files) {
+                    data.files = files;
+                    if (that._trigger('drop', e, data) !== false) {
+                        that._onAdd(e, data);
+                    }
+                });
+            }
+        },
+
+        _onDragOver: function (e) {
+            var dataTransfer = e.dataTransfer = e.originalEvent &&
+                e.originalEvent.dataTransfer;
+            if (dataTransfer) {
+                if (this._trigger('dragover', e) === false) {
+                    return false;
+                }
+                if ($.inArray('Files', dataTransfer.types) !== -1) {
+                    dataTransfer.dropEffect = 'copy';
+                    e.preventDefault();
+                }
+            }
+        },
+
+        _initEventHandlers: function () {
+            if (this._isXHRUpload(this.options)) {
+                this._on(this.options.dropZone, {
+                    dragover: this._onDragOver,
+                    drop: this._onDrop
+                });
+                this._on(this.options.pasteZone, {
+                    paste: this._onPaste
+                });
+            }
+            this._on(this.options.fileInput, {
+                change: this._onChange
+            });
+        },
+
+        _destroyEventHandlers: function () {
+            this._off(this.options.dropZone, 'dragover drop');
+            this._off(this.options.pasteZone, 'paste');
+            this._off(this.options.fileInput, 'change');
+        },
+
+        _setOption: function (key, value) {
+            var reinit = $.inArray(key, this._specialOptions) !== -1;
+            if (reinit) {
+                this._destroyEventHandlers();
+            }
+            this._super(key, value);
+            if (reinit) {
+                this._initSpecialOptions();
+                this._initEventHandlers();
+            }
+        },
+
+        _initSpecialOptions: function () {
+            var options = this.options;
+            if (options.fileInput === undefined) {
+                options.fileInput = this.element.is('input[type="file"]') ?
+                        this.element : this.element.find('input[type="file"]');
+            } else if (!(options.fileInput instanceof $)) {
+                options.fileInput = $(options.fileInput);
+            }
+            if (!(options.dropZone instanceof $)) {
+                options.dropZone = $(options.dropZone);
+            }
+            if (!(options.pasteZone instanceof $)) {
+                options.pasteZone = $(options.pasteZone);
+            }
+        },
+
+        _getRegExp: function (str) {
+            var parts = str.split('/'),
+                modifiers = parts.pop();
+            parts.shift();
+            return new RegExp(parts.join('/'), modifiers);
+        },
+
+        _isRegExpOption: function (key, value) {
+            return key !== 'url' && $.type(value) === 'string' &&
+                /^\/.*\/[igm]{0,3}$/.test(value);
+        },
+
+        _initDataAttributes: function () {
+            var that = this,
+                options = this.options;
+            // Initialize options set via HTML5 data-attributes:
+            $.each(
+                $(this.element[0].cloneNode(false)).data(),
+                function (key, value) {
+                    if (that._isRegExpOption(key, value)) {
+                        value = that._getRegExp(value);
+                    }
+                    options[key] = value;
+                }
+            );
+        },
+
+        _create: function () {
+            this._initDataAttributes();
+            this._initSpecialOptions();
+            this._slots = [];
+            this._sequence = this._getXHRPromise(true);
+            this._sending = this._active = 0;
+            this._initProgressObject(this);
+            this._initEventHandlers();
+        },
+
+        // This method is exposed to the widget API and allows to query
+        // the number of active uploads:
+        active: function () {
+            return this._active;
+        },
+
+        // This method is exposed to the widget API and allows to query
+        // the widget upload progress.
+        // It returns an object with loaded, total and bitrate properties
+        // for the running uploads:
+        progress: function () {
+            return this._progress;
+        },
+
+        // This method is exposed to the widget API and allows adding files
+        // using the fileupload API. The data parameter accepts an object which
+        // must have a files property and can contain additional options:
+        // .fileupload('add', {files: filesList});
+        add: function (data) {
+            var that = this;
+            if (!data || this.options.disabled) {
+                return;
+            }
+            if (data.fileInput && !data.files) {
+                this._getFileInputFiles(data.fileInput).always(function (files) {
+                    data.files = files;
+                    that._onAdd(null, data);
+                });
+            } else {
+                data.files = $.makeArray(data.files);
+                this._onAdd(null, data);
+            }
+        },
+
+        // This method is exposed to the widget API and allows sending files
+        // using the fileupload API. The data parameter accepts an object which
+        // must have a files or fileInput property and can contain additional options:
+        // .fileupload('send', {files: filesList});
+        // The method returns a Promise object for the file upload call.
+        send: function (data) {
+            if (data && !this.options.disabled) {
+                if (data.fileInput && !data.files) {
+                    var that = this,
+                        dfd = $.Deferred(),
+                        promise = dfd.promise(),
+                        jqXHR,
+                        aborted;
+                    promise.abort = function () {
+                        aborted = true;
+                        if (jqXHR) {
+                            return jqXHR.abort();
+                        }
+                        dfd.reject(null, 'abort', 'abort');
+                        return promise;
+                    };
+                    this._getFileInputFiles(data.fileInput).always(
+                        function (files) {
+                            if (aborted) {
+                                return;
+                            }
+                            data.files = files;
+                            jqXHR = that._onSend(null, data).then(
+                                function (result, textStatus, jqXHR) {
+                                    dfd.resolve(result, textStatus, jqXHR);
+                                },
+                                function (jqXHR, textStatus, errorThrown) {
+                                    dfd.reject(jqXHR, textStatus, errorThrown);
+                                }
+                            );
+                        }
+                    );
+                    return this._enhancePromise(promise);
+                }
+                data.files = $.makeArray(data.files);
+                if (data.files.length) {
+                    return this._onSend(null, data);
+                }
+            }
+            return this._getXHRPromise(false, data && data.context);
+        }
+
+    });
+
+}));
Index: admin/js/jsUpload/jquery.iframe-transport.js
===================================================================
--- admin/js/jsUpload/jquery.iframe-transport.js	(revision 1144)
+++ admin/js/jsUpload/jquery.iframe-transport.js	(revision 1144)
@@ -0,0 +1,191 @@
+/*
+ * jQuery Iframe Transport Plugin 1.6.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
+ */
+
+/*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) {
+            var form,
+                iframe,
+                addParamChar;
+            return {
+                send: function (_, completeCallback) {
+                    form = $('<form style="display:none;"></form>');
+                    form.attr('accept-charset', options.formAcceptCharset);
+                    addParamChar = /\?/.test(options.url) ? '&' : '?';
+                    // XDomainRequest only supports GET and POST:
+                    if (options.type === 'DELETE') {
+                        options.url = options.url + addParamChar + '_method=DELETE';
+                        options.type = 'POST';
+                    } else if (options.type === 'PUT') {
+                        options.url = options.url + addParamChar + '_method=PUT';
+                        options.type = 'POST';
+                    } else if (options.type === 'PATCH') {
+                        options.url = options.url + addParamChar + '_method=PATCH';
+                        options.type = 'POST';
+                    }
+                    // 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:
+                    counter += 1;
+                    iframe = $(
+                        '<iframe src="javascript:false;" name="iframe-transport-' +
+                            counter + '"></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);
+                                window.setTimeout(function () {
+                                    // Removing the form in a setTimeout call
+                                    // allows Chrome's developer tools to display
+                                    // the response result
+                                    form.remove();
+                                }, 0);
+                            });
+                        form
+                            .prop('target', iframe.prop('name'))
+                            .prop('action', options.url)
+                            .prop('method', options.type);
+                        if (options.formData) {
+                            $.each(options.formData, function (index, field) {
+                                $('<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 && $(iframe[0].body).text();
+            },
+            'iframe json': function (iframe) {
+                return iframe && $.parseJSON($(iframe[0].body).text());
+            },
+            'iframe html': function (iframe) {
+                return iframe && $(iframe[0].body).html();
+            },
+            'iframe script': function (iframe) {
+                return iframe && $.globalEval($(iframe[0].body).text());
+            }
+        }
+    });
+
+}));
Index: admin/js/jsUpload/load-image.js
===================================================================
--- admin/js/jsUpload/load-image.js	(revision 1144)
+++ admin/js/jsUpload/load-image.js	(revision 1144)
@@ -0,0 +1,381 @@
+/*
+ * JavaScript Load Image 1.6
+ * https://github.com/blueimp/JavaScript-Load-Image
+ *
+ * Copyright 2011, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * iOS image scaling fixes based on
+ * https://github.com/stomita/ios-imagefile-megapixel
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/*jslint nomen: true, bitwise: true */
+/*global window, document, URL, webkitURL, Blob, File, FileReader, define */
+
+(function ($) {
+    'use strict';
+
+    // Loads an image for a given File object.
+    // Invokes the callback with an img or optional canvas
+    // element (if supported by the browser) as parameter:
+    var loadImage = function (file, callback, options) {
+            var img = document.createElement('img'),
+                url,
+                oUrl;
+            img.onerror = callback;
+            img.onload = function () {
+                if (oUrl && !(options && options.noRevoke)) {
+                    loadImage.revokeObjectURL(oUrl);
+                }
+                if (callback) {
+                    callback(loadImage.scale(img, options));
+                }
+            };
+            if (loadImage.isInstanceOf('Blob', file) ||
+                    // Files are also Blob instances, but some browsers
+                    // (Firefox 3.6) support the File API but not Blobs:
+                    loadImage.isInstanceOf('File', file)) {
+                url = oUrl = loadImage.createObjectURL(file);
+                // Store the file type for resize processing:
+                img._type = file.type;
+            } else if (typeof file === 'string') {
+                url = file;
+                if (options && options.crossOrigin) {
+                    img.crossOrigin = options.crossOrigin;
+                }
+            } else {
+                return false;
+            }
+            if (url) {
+                img.src = url;
+                return img;
+            }
+            return loadImage.readFile(file, function (e) {
+                var target = e.target;
+                if (target && target.result) {
+                    img.src = target.result;
+                } else {
+                    if (callback) {
+                        callback(e);
+                    }
+                }
+            });
+        },
+        // The check for URL.revokeObjectURL fixes an issue with Opera 12,
+        // which provides URL.createObjectURL but doesn't properly implement it:
+        urlAPI = (window.createObjectURL && window) ||
+            (window.URL && URL.revokeObjectURL && URL) ||
+            (window.webkitURL && webkitURL);
+
+    loadImage.isInstanceOf = function (type, obj) {
+        // Cross-frame instanceof check
+        return Object.prototype.toString.call(obj) === '[object ' + type + ']';
+    };
+
+    // Transform image orientation based on the given EXIF orientation data:
+    loadImage.transformCoordinates = function (canvas, orientation) {
+        var ctx = canvas.getContext('2d'),
+            width = canvas.width,
+            height = canvas.height;
+        if (orientation > 4) {
+            canvas.width = height;
+            canvas.height = width;
+        }
+        switch (orientation) {
+        case 2:
+            // horizontal flip
+            ctx.translate(width, 0);
+            ctx.scale(-1, 1);
+            break;
+        case 3:
+            // 180 rotate left
+            ctx.translate(width, height);
+            ctx.rotate(Math.PI);
+            break;
+        case 4:
+            // vertical flip
+            ctx.translate(0, height);
+            ctx.scale(1, -1);
+            break;
+        case 5:
+            // vertical flip + 90 rotate right
+            ctx.rotate(0.5 * Math.PI);
+            ctx.scale(1, -1);
+            break;
+        case 6:
+            // 90 rotate right
+            ctx.rotate(0.5 * Math.PI);
+            ctx.translate(0, -height);
+            break;
+        case 7:
+            // horizontal flip + 90 rotate right
+            ctx.rotate(0.5 * Math.PI);
+            ctx.translate(width, -height);
+            ctx.scale(-1, 1);
+            break;
+        case 8:
+            // 90 rotate left
+            ctx.rotate(-0.5 * Math.PI);
+            ctx.translate(-width, 0);
+            break;
+        }
+    };
+
+    // Detects subsampling in JPEG images:
+    loadImage.detectSubsampling = function (img) {
+        var canvas,
+            context;
+        if (img.width * img.height > 1024 * 1024) { // only consider mexapixel images
+            canvas = document.createElement('canvas');
+            canvas.width = canvas.height = 1;
+            context = canvas.getContext('2d');
+            context.drawImage(img, -img.width + 1, 0);
+            // subsampled image becomes half smaller in rendering size.
+            // check alpha channel value to confirm image is covering edge pixel or not.
+            // if alpha value is 0 image is not covering, hence subsampled.
+            return context.getImageData(0, 0, 1, 1).data[3] === 0;
+        }
+        return false;
+    };
+
+    // Detects vertical squash in JPEG images:
+    loadImage.detectVerticalSquash = function (img, correctedHeight) {
+        var canvas = document.createElement('canvas'),
+            context = canvas.getContext('2d'),
+            data,
+            sy,
+            ey,
+            py,
+            alpha;
+        canvas.width = 1;
+        canvas.height = correctedHeight;
+        context.drawImage(img, 0, 0);
+        data = context.getImageData(0, 0, 1, correctedHeight).data;
+        // search image edge pixel position in case it is squashed vertically:
+        sy = 0;
+        ey = correctedHeight;
+        py = correctedHeight;
+        while (py > sy) {
+            alpha = data[(py - 1) * 4 + 3];
+            if (alpha === 0) {
+                ey = py;
+            } else {
+                sy = py;
+            }
+            py = (ey + sy) >> 1;
+        }
+        return (py / correctedHeight) || 1;
+    };
+
+    // Renders image to canvas while working around iOS image scaling bugs:
+    // https://github.com/blueimp/JavaScript-Load-Image/issues/13
+    loadImage.renderImageToCanvas = function (
+        canvas,
+        img,
+        sourceX,
+        sourceY,
+        sourceWidth,
+        sourceHeight,
+        destX,
+        destY,
+        destWidth,
+        destHeight
+    ) {
+        var context = canvas.getContext('2d'),
+            tmpCanvas = document.createElement('canvas'),
+            tileSize = tmpCanvas.width = tmpCanvas.height = 1024,
+            tmpContext = tmpCanvas.getContext('2d'),
+            vertSquashRatio,
+            tileX,
+            tileY;
+        context.save();
+        if (loadImage.detectSubsampling(img)) {
+            sourceWidth /= 2;
+            sourceHeight /= 2;
+        }
+        vertSquashRatio = loadImage.detectVerticalSquash(img, sourceHeight);
+        destWidth = Math.ceil(tileSize * destWidth / sourceWidth);
+        destHeight = Math.ceil(
+            tileSize * destHeight / sourceHeight / vertSquashRatio
+        );
+        destY = 0;
+        tileY = 0;
+        while (tileY < sourceHeight) {
+            destX = 0;
+            tileX = 0;
+            while (tileX < sourceWidth) {
+                tmpContext.clearRect(0, 0, tileSize, tileSize);
+                tmpContext.drawImage(
+                    img,
+                    sourceX,
+                    sourceY,
+                    sourceWidth,
+                    sourceHeight,
+                    -tileX,
+                    -tileY,
+                    sourceWidth,
+                    sourceHeight
+                );
+                context.drawImage(
+                    tmpCanvas,
+                    0,
+                    0,
+                    tileSize,
+                    tileSize,
+                    destX,
+                    destY,
+                    destWidth,
+                    destHeight
+                );
+                tileX += tileSize;
+                destX += destWidth;
+            }
+            tileY += tileSize;
+            destY += destHeight;
+        }
+        context.restore();
+    };
+
+    // Scales the given image (img or canvas HTML element)
+    // using the given options.
+    // Returns a canvas object if the browser supports canvas
+    // and the canvas or crop option is true or a canvas object
+    // is passed as image, else the scaled image:
+    loadImage.scale = function (img, options) {
+        options = options || {};
+        var canvas = document.createElement('canvas'),
+            useCanvas = img.getContext ||
+                ((options.canvas || options.crop || options.orientation) &&
+                    canvas.getContext),
+            width = img.width,
+            height = img.height,
+            sourceWidth = width,
+            sourceHeight = height,
+            sourceX = 0,
+            sourceY = 0,
+            destX = 0,
+            destY = 0,
+            maxWidth,
+            maxHeight,
+            minWidth,
+            minHeight,
+            destWidth,
+            destHeight,
+            scale;
+        if (useCanvas && options.orientation > 4) {
+            maxWidth = options.maxHeight;
+            maxHeight = options.maxWidth;
+            minWidth = options.minHeight;
+            minHeight = options.minWidth;
+        } else {
+            maxWidth = options.maxWidth;
+            maxHeight = options.maxHeight;
+            minWidth = options.minWidth;
+            minHeight = options.minHeight;
+        }
+        if (useCanvas && maxWidth && maxHeight && options.crop) {
+            destWidth = maxWidth;
+            destHeight = maxHeight;
+            if (width / height < maxWidth / maxHeight) {
+                sourceHeight = maxHeight * width / maxWidth;
+                sourceY = (height - sourceHeight) / 2;
+            } else {
+                sourceWidth = maxWidth * height / maxHeight;
+                sourceX = (width - sourceWidth) / 2;
+            }
+        } else {
+            destWidth = width;
+            destHeight = height;
+            scale = Math.max(
+                (minWidth || destWidth) / destWidth,
+                (minHeight || destHeight) / destHeight
+            );
+            if (scale > 1) {
+                destWidth = Math.ceil(destWidth * scale);
+                destHeight = Math.ceil(destHeight * scale);
+            }
+            scale = Math.min(
+                (maxWidth || destWidth) / destWidth,
+                (maxHeight || destHeight) / destHeight
+            );
+            if (scale < 1) {
+                destWidth = Math.ceil(destWidth * scale);
+                destHeight = Math.ceil(destHeight * scale);
+            }
+        }
+        if (useCanvas) {
+            canvas.width = destWidth;
+            canvas.height = destHeight;
+            loadImage.transformCoordinates(
+                canvas,
+                options.orientation
+            );
+            if (img._type === 'image/jpeg') {
+                loadImage.renderImageToCanvas(
+                    canvas,
+                    img,
+                    sourceX,
+                    sourceY,
+                    sourceWidth,
+                    sourceHeight,
+                    destX,
+                    destY,
+                    destWidth,
+                    destHeight
+                );
+            } else {
+                canvas.getContext('2d').drawImage(
+                    img,
+                    sourceX,
+                    sourceY,
+                    sourceWidth,
+                    sourceHeight,
+                    destX,
+                    destY,
+                    destWidth,
+                    destHeight
+                );
+            }
+            return canvas;
+        }
+        img.width = destWidth;
+        img.height = destHeight;
+        return img;
+    };
+
+    loadImage.createObjectURL = function (file) {
+        return urlAPI ? urlAPI.createObjectURL(file) : false;
+    };
+
+    loadImage.revokeObjectURL = function (url) {
+        return urlAPI ? urlAPI.revokeObjectURL(url) : false;
+    };
+
+    // Loads a given File object via FileReader interface,
+    // invokes the callback with the event object (load or error).
+    // The result can be read via event.target.result:
+    loadImage.readFile = function (file, callback, method) {
+        if (window.FileReader && FileReader.prototype.readAsDataURL) {
+            var fileReader = new FileReader();
+            fileReader.onload = fileReader.onerror = callback;
+            method = method || 'readAsDataURL';
+            if (!fileReader[method]) {
+                return false;
+            }
+            fileReader[method](file);
+            return fileReader;
+        }
+        return false;
+    };
+
+    if (typeof define === 'function' && define.amd) {
+        define(function () {
+            return loadImage;
+        });
+    } else {
+        $.loadImage = loadImage;
+    }
+}(this));
Index: admin/js/jsUpload/tmpl.js
===================================================================
--- admin/js/jsUpload/tmpl.js	(revision 1144)
+++ admin/js/jsUpload/tmpl.js	(revision 1144)
@@ -0,0 +1,86 @@
+/*
+ * JavaScript Templates 2.1.0
+ * https://github.com/blueimp/JavaScript-Templates
+ *
+ * Copyright 2011, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ *
+ * Inspired by John Resig's JavaScript Micro-Templating:
+ * http://ejohn.org/blog/javascript-micro-templating/
+ */
+
+/*jslint evil: true, regexp: true */
+/*global document, define */
+
+(function ($) {
+    "use strict";
+    var tmpl = function (str, data) {
+        var f = !/[^\w\-\.:]/.test(str) ? tmpl.cache[str] = tmpl.cache[str] ||
+                tmpl(tmpl.load(str)) :
+                    new Function(
+                        tmpl.arg + ',tmpl',
+                        "var _e=tmpl.encode" + tmpl.helper + ",_s='" +
+                            str.replace(tmpl.regexp, tmpl.func) +
+                            "';return _s;"
+                    );
+        return data ? f(data, tmpl) : function (data) {
+            return f(data, tmpl);
+        };
+    };
+    tmpl.cache = {};
+    tmpl.load = function (id) {
+        return document.getElementById(id).innerHTML;
+    };
+    tmpl.regexp = /([\s'\\])(?![^%]*%\})|(?:\{%(=|#)([\s\S]+?)%\})|(\{%)|(%\})/g;
+    tmpl.func = function (s, p1, p2, p3, p4, p5) {
+        if (p1) { // whitespace, quote and backspace in interpolation context
+            return {
+                "\n": "\\n",
+                "\r": "\\r",
+                "\t": "\\t",
+                " " : " "
+            }[s] || "\\" + s;
+        }
+        if (p2) { // interpolation: {%=prop%}, or unescaped: {%#prop%}
+            if (p2 === "=") {
+                return "'+_e(" + p3 + ")+'";
+            }
+            return "'+(" + p3 + "||'')+'";
+        }
+        if (p4) { // evaluation start tag: {%
+            return "';";
+        }
+        if (p5) { // evaluation end tag: %}
+            return "_s+='";
+        }
+    };
+    tmpl.encReg = /[<>&"'\x00]/g;
+    tmpl.encMap = {
+        "<"   : "&lt;",
+        ">"   : "&gt;",
+        "&"   : "&amp;",
+        "\""  : "&quot;",
+        "'"   : "&#39;"
+    };
+    tmpl.encode = function (s) {
+        return String(s || "").replace(
+            tmpl.encReg,
+            function (c) {
+                return tmpl.encMap[c] || "";
+            }
+        );
+    };
+    tmpl.arg = "o";
+    tmpl.helper = ",print=function(s,e){_s+=e&&(s||'')||_e(s);}" +
+        ",include=function(s,d){_s+=tmpl(s,d);}";
+    if (typeof define === "function" && define.amd) {
+        define(function () {
+            return tmpl;
+        });
+    } else {
+        $.tmpl = tmpl;
+    }
+}(this));
Index: admin/js/jsUpload/vendor/jquery.ui.widget.js
===================================================================
--- admin/js/jsUpload/vendor/jquery.ui.widget.js	(revision 1144)
+++ admin/js/jsUpload/vendor/jquery.ui.widget.js	(revision 1144)
@@ -0,0 +1,530 @@
+/*
+ * jQuery UI Widget 1.10.1+amd
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/jQuery.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 ) {
+
+var uuid = 0,
+	slice = Array.prototype.slice,
+	_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 );
+};
+
+$.widget = function( name, base, prototype ) {
+	var fullName, existingConstructor, constructor, basePrototype,
+		// proxiedPrototype allows the provided prototype to remain unmodified
+		// so that it can be used as a mixin for multiple widgets (#8876)
+		proxiedPrototype = {},
+		namespace = name.split( "." )[ 0 ];
+
+	name = name.split( "." )[ 1 ];
+	fullName = namespace + "-" + name;
+
+	if ( !prototype ) {
+		prototype = base;
+		base = $.Widget;
+	}
+
+	// create selector for plugin
+	$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
+		return !!$.data( elem, fullName );
+	};
+
+	$[ namespace ] = $[ namespace ] || {};
+	existingConstructor = $[ namespace ][ name ];
+	constructor = $[ namespace ][ name ] = function( options, element ) {
+		// allow instantiation without "new" keyword
+		if ( !this._createWidget ) {
+			return new constructor( options, element );
+		}
+
+		// allow instantiation without initializing for simple inheritance
+		// must use "new" keyword (the code above always passes args)
+		if ( arguments.length ) {
+			this._createWidget( options, element );
+		}
+	};
+	// extend with the existing constructor to carry over any static properties
+	$.extend( constructor, existingConstructor, {
+		version: prototype.version,
+		// copy the object used to create the prototype in case we need to
+		// redefine the widget later
+		_proto: $.extend( {}, prototype ),
+		// track widgets that inherit from this widget in case this widget is
+		// redefined after a widget inherits from it
+		_childConstructors: []
+	});
+
+	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
+	basePrototype.options = $.widget.extend( {}, basePrototype.options );
+	$.each( prototype, function( prop, value ) {
+		if ( !$.isFunction( value ) ) {
+			proxiedPrototype[ prop ] = value;
+			return;
+		}
+		proxiedPrototype[ prop ] = (function() {
+			var _super = function() {
+					return base.prototype[ prop ].apply( this, arguments );
+				},
+				_superApply = function( args ) {
+					return base.prototype[ prop ].apply( this, args );
+				};
+			return function() {
+				var __super = this._super,
+					__superApply = this._superApply,
+					returnValue;
+
+				this._super = _super;
+				this._superApply = _superApply;
+
+				returnValue = value.apply( this, arguments );
+
+				this._super = __super;
+				this._superApply = __superApply;
+
+				return returnValue;
+			};
+		})();
+	});
+	constructor.prototype = $.widget.extend( basePrototype, {
+		// TODO: remove support for widgetEventPrefix
+		// always use the name + a colon as the prefix, e.g., draggable:start
+		// don't prefix for widgets that aren't DOM-based
+		widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name
+	}, proxiedPrototype, {
+		constructor: constructor,
+		namespace: namespace,
+		widgetName: name,
+		widgetFullName: fullName
+	});
+
+	// If this widget is being redefined then we need to find all widgets that
+	// are inheriting from it and redefine all of them so that they inherit from
+	// the new version of this widget. We're essentially trying to replace one
+	// level in the prototype chain.
+	if ( existingConstructor ) {
+		$.each( existingConstructor._childConstructors, function( i, child ) {
+			var childPrototype = child.prototype;
+
+			// redefine the child widget using the same prototype that was
+			// originally used, but inherit from the new version of the base
+			$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
+		});
+		// remove the list of existing child constructors from the old constructor
+		// so the old child constructors can be garbage collected
+		delete existingConstructor._childConstructors;
+	} else {
+		base._childConstructors.push( constructor );
+	}
+
+	$.widget.bridge( name, constructor );
+};
+
+$.widget.extend = function( target ) {
+	var input = slice.call( arguments, 1 ),
+		inputIndex = 0,
+		inputLength = input.length,
+		key,
+		value;
+	for ( ; inputIndex < inputLength; inputIndex++ ) {
+		for ( key in input[ inputIndex ] ) {
+			value = input[ inputIndex ][ key ];
+			if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
+				// Clone objects
+				if ( $.isPlainObject( value ) ) {
+					target[ key ] = $.isPlainObject( target[ key ] ) ?
+						$.widget.extend( {}, target[ key ], value ) :
+						// Don't extend strings, arrays, etc. with objects
+						$.widget.extend( {}, value );
+				// Copy everything else by reference
+				} else {
+					target[ key ] = value;
+				}
+			}
+		}
+	}
+	return target;
+};
+
+$.widget.bridge = function( name, object ) {
+	var fullName = object.prototype.widgetFullName || name;
+	$.fn[ name ] = function( options ) {
+		var isMethodCall = typeof options === "string",
+			args = slice.call( arguments, 1 ),
+			returnValue = this;
+
+		// allow multiple hashes to be passed on init
+		options = !isMethodCall && args.length ?
+			$.widget.extend.apply( null, [ options ].concat(args) ) :
+			options;
+
+		if ( isMethodCall ) {
+			this.each(function() {
+				var methodValue,
+					instance = $.data( this, fullName );
+				if ( !instance ) {
+					return $.error( "cannot call methods on " + name + " prior to initialization; " +
+						"attempted to call method '" + options + "'" );
+				}
+				if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
+					return $.error( "no such method '" + options + "' for " + name + " widget instance" );
+				}
+				methodValue = instance[ options ].apply( instance, args );
+				if ( methodValue !== instance && methodValue !== undefined ) {
+					returnValue = methodValue && methodValue.jquery ?
+						returnValue.pushStack( methodValue.get() ) :
+						methodValue;
+					return false;
+				}
+			});
+		} else {
+			this.each(function() {
+				var instance = $.data( this, fullName );
+				if ( instance ) {
+					instance.option( options || {} )._init();
+				} else {
+					$.data( this, fullName, new object( options, this ) );
+				}
+			});
+		}
+
+		return returnValue;
+	};
+};
+
+$.Widget = function( /* options, element */ ) {};
+$.Widget._childConstructors = [];
+
+$.Widget.prototype = {
+	widgetName: "widget",
+	widgetEventPrefix: "",
+	defaultElement: "<div>",
+	options: {
+		disabled: false,
+
+		// callbacks
+		create: null
+	},
+	_createWidget: function( options, element ) {
+		element = $( element || this.defaultElement || this )[ 0 ];
+		this.element = $( element );
+		this.uuid = uuid++;
+		this.eventNamespace = "." + this.widgetName + this.uuid;
+		this.options = $.widget.extend( {},
+			this.options,
+			this._getCreateOptions(),
+			options );
+
+		this.bindings = $();
+		this.hoverable = $();
+		this.focusable = $();
+
+		if ( element !== this ) {
+			$.data( element, this.widgetFullName, this );
+			this._on( true, this.element, {
+				remove: function( event ) {
+					if ( event.target === element ) {
+						this.destroy();
+					}
+				}
+			});
+			this.document = $( element.style ?
+				// element within the document
+				element.ownerDocument :
+				// element is window or document
+				element.document || element );
+			this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
+		}
+
+		this._create();
+		this._trigger( "create", null, this._getCreateEventData() );
+		this._init();
+	},
+	_getCreateOptions: $.noop,
+	_getCreateEventData: $.noop,
+	_create: $.noop,
+	_init: $.noop,
+
+	destroy: function() {
+		this._destroy();
+		// we can probably remove the unbind calls in 2.0
+		// all event bindings should go through this._on()
+		this.element
+			.unbind( this.eventNamespace )
+			// 1.9 BC for #7810
+			// TODO remove dual storage
+			.removeData( this.widgetName )
+			.removeData( this.widgetFullName )
+			// support: jquery <1.6.3
+			// http://bugs.jquery.com/ticket/9413
+			.removeData( $.camelCase( this.widgetFullName ) );
+		this.widget()
+			.unbind( this.eventNamespace )
+			.removeAttr( "aria-disabled" )
+			.removeClass(
+				this.widgetFullName + "-disabled " +
+				"ui-state-disabled" );
+
+		// clean up events and states
+		this.bindings.unbind( this.eventNamespace );
+		this.hoverable.removeClass( "ui-state-hover" );
+		this.focusable.removeClass( "ui-state-focus" );
+	},
+	_destroy: $.noop,
+
+	widget: function() {
+		return this.element;
+	},
+
+	option: function( key, value ) {
+		var options = key,
+			parts,
+			curOption,
+			i;
+
+		if ( arguments.length === 0 ) {
+			// don't return a reference to the internal hash
+			return $.widget.extend( {}, this.options );
+		}
+
+		if ( typeof key === "string" ) {
+			// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
+			options = {};
+			parts = key.split( "." );
+			key = parts.shift();
+			if ( parts.length ) {
+				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
+				for ( i = 0; i < parts.length - 1; i++ ) {
+					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
+					curOption = curOption[ parts[ i ] ];
+				}
+				key = parts.pop();
+				if ( value === undefined ) {
+					return curOption[ key ] === undefined ? null : curOption[ key ];
+				}
+				curOption[ key ] = value;
+			} else {
+				if ( value === undefined ) {
+					return this.options[ key ] === undefined ? null : this.options[ key ];
+				}
+				options[ key ] = value;
+			}
+		}
+
+		this._setOptions( options );
+
+		return this;
+	},
+	_setOptions: function( options ) {
+		var key;
+
+		for ( key in options ) {
+			this._setOption( key, options[ key ] );
+		}
+
+		return this;
+	},
+	_setOption: function( key, value ) {
+		this.options[ key ] = value;
+
+		if ( key === "disabled" ) {
+			this.widget()
+				.toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
+				.attr( "aria-disabled", value );
+			this.hoverable.removeClass( "ui-state-hover" );
+			this.focusable.removeClass( "ui-state-focus" );
+		}
+
+		return this;
+	},
+
+	enable: function() {
+		return this._setOption( "disabled", false );
+	},
+	disable: function() {
+		return this._setOption( "disabled", true );
+	},
+
+	_on: function( suppressDisabledCheck, element, handlers ) {
+		var delegateElement,
+			instance = this;
+
+		// no suppressDisabledCheck flag, shuffle arguments
+		if ( typeof suppressDisabledCheck !== "boolean" ) {
+			handlers = element;
+			element = suppressDisabledCheck;
+			suppressDisabledCheck = false;
+		}
+
+		// no element argument, shuffle and use this.element
+		if ( !handlers ) {
+			handlers = element;
+			element = this.element;
+			delegateElement = this.widget();
+		} else {
+			// accept selectors, DOM elements
+			element = delegateElement = $( element );
+			this.bindings = this.bindings.add( element );
+		}
+
+		$.each( handlers, function( event, handler ) {
+			function handlerProxy() {
+				// allow widgets to customize the disabled handling
+				// - disabled as an array instead of boolean
+				// - disabled class as method for disabling individual parts
+				if ( !suppressDisabledCheck &&
+						( instance.options.disabled === true ||
+							$( this ).hasClass( "ui-state-disabled" ) ) ) {
+					return;
+				}
+				return ( typeof handler === "string" ? instance[ handler ] : handler )
+					.apply( instance, arguments );
+			}
+
+			// copy the guid so direct unbinding works
+			if ( typeof handler !== "string" ) {
+				handlerProxy.guid = handler.guid =
+					handler.guid || handlerProxy.guid || $.guid++;
+			}
+
+			var match = event.match( /^(\w+)\s*(.*)$/ ),
+				eventName = match[1] + instance.eventNamespace,
+				selector = match[2];
+			if ( selector ) {
+				delegateElement.delegate( selector, eventName, handlerProxy );
+			} else {
+				element.bind( eventName, handlerProxy );
+			}
+		});
+	},
+
+	_off: function( element, eventName ) {
+		eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
+		element.unbind( eventName ).undelegate( eventName );
+	},
+
+	_delay: function( handler, delay ) {
+		function handlerProxy() {
+			return ( typeof handler === "string" ? instance[ handler ] : handler )
+				.apply( instance, arguments );
+		}
+		var instance = this;
+		return setTimeout( handlerProxy, delay || 0 );
+	},
+
+	_hoverable: function( element ) {
+		this.hoverable = this.hoverable.add( element );
+		this._on( element, {
+			mouseenter: function( event ) {
+				$( event.currentTarget ).addClass( "ui-state-hover" );
+			},
+			mouseleave: function( event ) {
+				$( event.currentTarget ).removeClass( "ui-state-hover" );
+			}
+		});
+	},
+
+	_focusable: function( element ) {
+		this.focusable = this.focusable.add( element );
+		this._on( element, {
+			focusin: function( event ) {
+				$( event.currentTarget ).addClass( "ui-state-focus" );
+			},
+			focusout: function( event ) {
+				$( event.currentTarget ).removeClass( "ui-state-focus" );
+			}
+		});
+	},
+
+	_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.apply( this.element[0], [ event ].concat( data ) ) === false ||
+			event.isDefaultPrevented() );
+	}
+};
+
+$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
+	$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
+		if ( typeof options === "string" ) {
+			options = { effect: options };
+		}
+		var hasOptions,
+			effectName = !options ?
+				method :
+				options === true || typeof options === "number" ?
+					defaultEffect :
+					options.effect || defaultEffect;
+		options = options || {};
+		if ( typeof options === "number" ) {
+			options = { duration: options };
+		}
+		hasOptions = !$.isEmptyObject( options );
+		options.complete = callback;
+		if ( options.delay ) {
+			element.delay( options.delay );
+		}
+		if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
+			element[ method ]( options );
+		} else if ( effectName !== method && element[ effectName ] ) {
+			element[ effectName ]( options.duration, options.easing, callback );
+		} else {
+			element.queue(function( next ) {
+				$( this )[ method ]();
+				if ( callback ) {
+					callback.call( element[ 0 ] );
+				}
+				next();
+			});
+		}
+	};
+});
+
+}));
Index: admin/media.php
===================================================================
--- admin/media.php	(revision 1039)
+++ admin/media.php	(revision 1145)
@@ -10,45 +10,4 @@
 #
 # -- END LICENSE BLOCK -----------------------------------------
-
-/* Upload backend
--------------------------------------------------------- */
-if (!empty($_POST['swfupload']))
-{
-	header('content-type: text/plain');
-	try
-	{
-		if (empty($_POST['sess_id']) || empty($_POST['sess_uid'])) {
-			throw new Exception('No credentials.');
-		}
-		define('DC_AUTH_SESS_ID',$_POST['sess_id']);
-		define('DC_AUTH_SESS_UID',$_POST['sess_uid']);
-		
-		require dirname(__FILE__).'/../inc/admin/prepend.php';
-		
-		if (!$core->auth->check('media,media_admin',$core->blog->id)) {
-			throw new Exception('Permission denied.');
-		}
-		
-		$d = isset($_POST['d']) ? $_POST['d'] : null;
-		$core->media = new dcMedia($core);
-		$core->media->chdir($d);
-		$core->media->getDir();
-		$dir =& $core->media->dir;
-		
-		if (empty($_FILES['Filedata'])) {
-			throw new Exception('No file to upload.');
-		}
-		
-		files::uploadStatus($_FILES['Filedata']);
-		$core->media->uploadFile($_FILES['Filedata']['tmp_name'],$_FILES['Filedata']['name']);
-		
-		echo 'ok';
-	}
-	catch (Exception $e) {
-		echo __('Error:').' '.__($e->getMessage());
-	}
-	exit;
-}
-
 
 /* HTML page
@@ -175,20 +134,42 @@
 if ($dir && !empty($_FILES['upfile']))
 {
-	try
-	{
-		files::uploadStatus($_FILES['upfile']);
-		
-		$f_title = (isset($_POST['upfiletitle']) ? $_POST['upfiletitle'] : '');
-		$f_private = (isset($_POST['upfilepriv']) ? $_POST['upfilepriv'] : false);
-		
-		$core->media->uploadFile($_FILES['upfile']['tmp_name'],$_FILES['upfile']['name'],$f_title,$f_private);
-		http::redirect($page_url.'&d='.rawurlencode($d).'&upok=1');
-	}
-	catch (Exception $e)
-	{
-		$core->error->add($e->getMessage());
-	}
-}
-
+  $upfile = array('name' => $_FILES['upfile']['name'][0],
+                  'type' => $_FILES['upfile']['type'][0],
+                  'tmp_name' => $_FILES['upfile']['tmp_name'][0],
+                  'error' => $_FILES['upfile']['error'][0],
+                  'size' => $_FILES['upfile']['size'][0]
+                  );
+
+	try {
+    files::uploadStatus($upfile);
+
+    $core->media->uploadFile($upfile['tmp_name'],$upfile['name']);
+
+    if (!empty($_SERVER['HTTP_X_REQUESTED_WITH'])) {
+      header('Content-type: application/json');
+      $message = array();
+      $message['files'][] = array('name' => $upfile['name'],
+                                  'size' => $upfile['size']
+                                  );
+
+      echo json_encode($message);
+      exit();
+    } else {
+        http::redirect($page_url.'&d='.rawurlencode($d).'&upok=1');
+    }
+	} catch (Exception $e) {
+    if (!empty($_SERVER['HTTP_X_REQUESTED_WITH'])) {
+      header('Content-type: application/json');
+      $message = array();
+      $message['files'][] = array('name' => $upfile['name'],
+                                  'error' => $e->getMessage()
+                                  );
+      echo json_encode($message);
+      exit();
+    } else {
+      $core->error->add($e->getMessage());
+    }
+	}
+}
 
 # Removing item
@@ -215,5 +196,4 @@
 	}
 }
-
 
 # DISPLAY confirm page for rmdir & rmfile
@@ -245,11 +225,6 @@
 
 call_user_func($open_f,__('Media manager'),
-	'<script type="text/javascript">'."\n".
-	"//<![CDATA["."\n".
-	dcPage::jsVar('dotclear.candyUpload_force_init',$user_ui_enhanceduploader)."\n".
-	"//]]>".
-	"</script>".
 	dcPage::jsLoad('js/_media.js').
-	($core_media_writable ? dcPage::jsCandyUpload(array('d='.$d)) : '')
+	($core_media_writable ? dcPage::jsUpload(array('d='.$d)) : '')
 	);
 
@@ -339,4 +314,5 @@
 }
 
+
 if ($core_media_writable)
 {
@@ -344,28 +320,22 @@
 	
 	echo
-	'<div class="col">'.
-	'<fieldset id="add-file-f"><legend>'.__('Add files').'</legend>'.
-	'<p>'.__('Please take care to publish media that you own and that are not protected by copyright.').'</p>'.
-	'<form id="media-upload" class="clear" action="'.html::escapeURL($page_url).'" method="post" enctype="multipart/form-data">'.
-	'<div>'.form::hidden(array('MAX_FILE_SIZE'),DC_MAX_UPLOAD_SIZE).
-	$core->formNonce().'</div>'.
-	'<p><label for="upfile">'.__('Choose a file:').
-	' ('.sprintf(__('Maximum size %s'),files::size(DC_MAX_UPLOAD_SIZE)).')'.
-	'<input type="file" id="upfile" name="upfile" size="20" />'.
-	'</label></p>'.
-	'<p><label for="upfiletitle">'.__('Title:').form::field(array('upfiletitle','upfiletitle'),35,255).'</label></p>'.
-	'<p><label for="upfilepriv" class="classic">'.form::checkbox(array('upfilepriv','upfilepriv'),1).' '.
-	__('Private').'</label></p>';
-	if (!$user_ui_enhanceduploader) {
-		echo
-		'<p class="form-help info">'.__('To send several files at the same time, you can activate the enhanced uploader in').
-		' <a href="preferences.php?tab=user-options">'.__('My preferences').'</a></p>';
-	}
-	echo
-	'<p><input type="submit" value="'.__('Send').'" />'.
-	form::hidden(array('d'),$d).'</p>'.
-	'</fieldset>'.
-	'</form>'.
-	'</div>';
+  '<div class="col">'.
+  '<fieldset id="add-file-f"><legend>'.__('Add files').'</legend>'.
+  '<p>'.__('Please take care to publish media that you own and that are not protected by copyright.').'</p>'.
+  ' <form id="fileupload" action="'.html::escapeURL($page_url).'" method="POST" enctype="multipart/form-data">'.
+  '<div>'.form::hidden(array('MAX_FILE_SIZE'),DC_MAX_UPLOAD_SIZE).
+    $core->formNonce().'</div>'.
+  '<div class="fileupload-buttonbar">'.
+  '<label class="button-add button" for="upfile">'.__('Add files').
+  '<input type="file" id="upfile" name="upfile[]" multiple="multiple" data-url="'.html::escapeURL($page_url).'" />'.
+  '</label>'.
+  '</span>'.
+  '<input class="button start" type="submit" value="'.__('Send').'"/>'.
+  '</div>'.
+  '<table role="presentation" class="table table-striped"><tbody class="files" data-toggle="modal-gallery" data-target="#modal-gallery"></tbody></table>'.
+    form::hidden(array('d'),$d).'</p>'.
+  '</fieldset>'.
+  '</form>'.
+  '</div>';
 	
 	echo
Index: dmin/style/candyUpload/style.css
===================================================================
--- admin/style/candyUpload/style.css	(revision 1065)
+++ 	(revision )
@@ -1,113 +1,0 @@
-
-.cu-ctrl {
-	background: #f5f5f5;
-	padding: .5em 0 0 0;
-	margin: 0 0 1em 0;
-	overflow: hidden;
-}
-.cu-msg {
-	font-weight: bold;
-}
-.cu-msg.cu-error {
-	color: #c00;
-}
-.cu-files {
-	padding: 0 0.5em;
-	margin: 10px 0;
-}
-.cu-file {
-	margin: 0 0 8px 0;
-	position: relative;
-}
-.cu-fileinfo {
-	margin-left: 16px;
-}
-.cu-fileinfo span.cu-filecancel {
-	display: block;
-	position: absolute;
-	top: 2px;
-	left: 0px !important;
-	left: -16px;
-}
-.cu-filecancel a {
-	display: block;
-	width: 12px;
-	height: 12px;
-	background: transparent url(cancel.png) no-repeat top left;
-	border: none;
-	text-indent: -5000px;
-	outline: none;
-}
-.cu-filemsg {
-	font-weight: bold;
-	color: green;
-}
-span.cu-filemsg.cu-error {
-	color: #c00;
-}
-.cu-progress {
-	margin-left: 16px;
-}
-.cu-progress div {
-	height: 10px;
-	width: 0;
-	font-size: 0.8em;
-	line-height: 1em;
-	height: 1em;
-	padding: .2em 0;
-	text-align: right;
-	background: green url(loader.png) repeat-x top left;
-	color: white;
-	font-weight: bold;
-	border-radius: 2px;
-}
-.cu-btn {
-	padding: 0 0.5em;
-	line-height: 1.2em;
-	height: 1.5em;
-	margin-top: 1em;
-	position: relative;
-}
-.cu-btn span {
-	display: block;
-	margin: 0 1em 0 0;
-	float: right;
-}
-.cu-btn span a {
-	display: block;
-	padding: .3em 1.5em;
-}
-span.cu-btn-browse {
-	float: none;
-	position: absolute;
-	left: 0;
-	margin: 0;
-}
-span.cu-btn-upload, span.cu-btn-clean {
-	margin-right: 0;
-}
-span.cu-btn-upload a.button {
-	color: #fff;
-	border: 1px solid #2373A8;
-	background: #2373A8;
-	background: -webkit-gradient(linear, left top, left bottom, from(#2C8FD1), to(#2373A8));
-	background: -moz-linear-gradient(top,  #2C8FD1,  #2373A8);
-}
-span.cu-btn-upload a.button:hover, 
-span.cu-btn-upload a.button:focus {
-	color: #fff;
-	border: 1px solid #2C8FD1;
-	background: #2C8FD1;
-	background: -webkit-gradient(linear, left top, left bottom, from(#2373A8), to(#2C8FD1));
-	background: -moz-linear-gradient(top,  #2373A8,  #2C8FD1);
-	filter:  progid:DXImageTransform.Microsoft.gradient(startColorstr='#2373A8', endColorstr='#2C8FD1');
-}
-.cu-maxsize.form-note {
-	clear: both;
-	margin: 1.5em 0 0;
-	font-style: italic;
-}
-.cu-disable {
-	margin: 1em 0;
-	padding: 0 0 0.5em 0.5em;
-}
Index: admin/style/jsUpload/style.css
===================================================================
--- admin/style/jsUpload/style.css	(revision 1145)
+++ admin/style/jsUpload/style.css	(revision 1145)
@@ -0,0 +1,54 @@
+.button {
+  display: inline-block;
+  *display: inline;
+  border-radius: .2em;
+  background: #2373A8;
+  background: -webkit-gradient(linear, left top, left bottom, from(#2373A8), to(#2C8FD1));
+  background: -moz-linear-gradient(top,  #2373A8,  #2C8FD1);
+  outline: none;
+  border: 1px solid #2373A8;
+  text-decoration: none;
+  text-shadow: 0 1px 1px rgba(0,0,0,.3);
+  text-align: center;
+  cursor: pointer;
+  color: #fff;
+  margin-right: 4px;
+}
+
+.button-add { 
+  position: relative;
+}
+
+.button:hover {
+  background: #2373A8;
+  border: 1px solid #2C8FD1;
+}
+
+.button-add input {
+  position: absolute;
+  top: 0;
+  right: 0;
+  margin: 0;
+  opacity: 0;
+  filter: alpha(opacity=0);
+  cursor: pointer;
+}
+
+.progress .bar {
+  background: transparent url('loader.png') repeat left top;
+}
+
+.upload-status { 
+  margin-top: 3px;
+  width: 13px;
+  height: 13px;
+  display: block;
+}
+
+.upload-status.ok { 
+  background: transparent url('../../images/check-on.png') no-repeat left top;
+}
+
+.upload-status.error { 
+  background: transparent url('../../images/check-off.png') no-repeat left top;
+}
Index: inc/admin/lib.dc.page.php
===================================================================
--- inc/admin/lib.dc.page.php	(revision 1155)
+++ inc/admin/lib.dc.page.php	(revision 1157)
@@ -450,5 +450,5 @@
 	}
 	
-	public static function jsCandyUpload($params=array(),$base_url=null)
+	public static function jsUpload($params=array(),$base_url=null)
 	{
 		if (!$base_url) {
@@ -463,31 +463,91 @@
 		
 		return
-		'<link rel="stylesheet" type="text/css" href="style/candyUpload/style.css" />'."\n".
-		self::jsLoad('js/jquery/jquery.candyUpload.js').
-		
-		'<script type="text/javascript">'."\n".
-		"//<![CDATA[\n".
-		"dotclear.candyUpload = {};\n".
-		self::jsVar('dotclear.msg.activate_enhanced_uploader',__('Temporarily activate enhanced uploader')).
-		self::jsVar('dotclear.msg.disable_enhanced_uploader',__('Temporarily disable enhanced uploader')).
-		self::jsVar('$._candyUpload.prototype.locales.file_uploaded',__('File successfully uploaded.')).
-		self::jsVar('$._candyUpload.prototype.locales.max_file_size',__('Maximum file size allowed:')).
-		self::jsVar('$._candyUpload.prototype.locales.limit_exceeded',__('Limit exceeded.')).
-		self::jsVar('$._candyUpload.prototype.locales.size_limit_exceeded',__('File size exceeds allowed limit.')).
-		self::jsVar('$._candyUpload.prototype.locales.canceled',__('Canceled.')).
-		self::jsVar('$._candyUpload.prototype.locales.http_error',__('HTTP Error:')).
-		self::jsVar('$._candyUpload.prototype.locales.error',__('Error:')).
-		self::jsVar('$._candyUpload.prototype.locales.choose_file',__('Choose file')).
-		self::jsVar('$._candyUpload.prototype.locales.choose_files',__('Choose files')).
-		self::jsVar('$._candyUpload.prototype.locales.cancel',__('Cancel')).
-		self::jsVar('$._candyUpload.prototype.locales.clean',__('Clean')).
-		self::jsVar('$._candyUpload.prototype.locales.upload',__('Upload')).
-		self::jsVar('$._candyUpload.prototype.locales.no_file_in_queue',__('No file in queue.')).
-		self::jsVar('$._candyUpload.prototype.locales.file_in_queue',__('1 file in queue.')).
-		self::jsVar('$._candyUpload.prototype.locales.files_in_queue',__('%d files in queue.')).
-		self::jsVar('$._candyUpload.prototype.locales.queue_error',__('Queue error:')).
-		self::jsVar('dotclear.candyUpload.base_url',$base_url).
-		self::jsVar('dotclear.candyUpload.movie_url',$base_url.'index.php?pf=swfupload.swf').
-		self::jsVar('dotclear.candyUpload.params',implode('&',$params)).
+		'<link rel="stylesheet" type="text/css" href="style/jsUpload/style.css" />'."\n".
+
+    '<script id="template-upload" type="text/x-tmpl">
+{% for (var i=0, file; file=o.files[i]; i++) { %}
+    <tr class="template-upload fade">
+        <td>
+            <span class="preview"></span>
+        </td>
+        <td>
+            <p class="name">{%=file.name%}</p>
+            {% if (file.error) { %}
+                <div><span class="label label-error">'.__('Error:').'</span> {%=file.error%}</div>
+            {% } %}
+        </td>
+        <td>
+            <p class="size">{%=o.formatFileSize(file.size)%}</p>
+            {% if (!o.files.error) { %}
+                <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>
+            {% if (!o.files.error && !i && !o.options.autoUpload) { %}
+                <input type="submit" class="button start" value="'.__('Send').'"/>
+            {% } %}
+        </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">
+        <td>
+            <span class="preview">
+                {% if (file.thumbnail_url) { %}
+                    <a href="{%=file.url%}" title="{%=file.name%}" data-gallery="gallery" download="{%=file.name%}"><img src="{%=file.thumbnail_url%}"></a>
+                {% } %}
+            </span>
+        </td>
+        <td>
+            <p class="name">{%=file.name%}</p>
+            {% if (file.error) { %}
+                <div><span class="label label-important">'.__('Error:').'</span> {%=file.error%}</div>
+            {% } %}
+        </td>
+        <td>
+            <span class="size">{%=o.formatFileSize(file.size)%}</span>
+        </td>
+        <td>
+            {% if (file.error) { %}
+            <span class="upload-status error"></span>
+            {% } else { %}
+            <span class="upload-status ok"></span>
+            {% } %}
+        </td>
+    </tr>
+{% } %}
+</script>'.
+
+		self::jsLoad('js/jsUpload/vendor/jquery.ui.widget.js').
+    self::jsLoad('js/jsUpload/tmpl.js').
+    self::jsLoad('js/jsUpload/load-image.js').
+		self::jsLoad('js/jsUpload/jquery.iframe-transport.js').
+		self::jsLoad('js/jsUpload/jquery.fileupload.js').
+		self::jsLoad('js/jsUpload/jquery.fileupload-process.js').
+		self::jsLoad('js/jsUpload/jquery.fileupload-resize.js').
+		self::jsLoad('js/jsUpload/jquery.fileupload-ui.js').
+		
+		'<script type="text/javascript">'."\n".
+		"//<![CDATA[\n".
+		"dotclear.jsUpload = {};\n".
+		"dotclear.jsUpload.msg = {};\n".
+		self::jsVar('dotclear.jsUpload.msg.limit_exceeded',__('Limit exceeded.')).
+		self::jsVar('dotclear.jsUpload.msg.size_limit_exceeded',__('File size exceeds allowed limit.')).
+		self::jsVar('dotclear.jsUpload.msg.canceled',__('Canceled.')).
+		self::jsVar('dotclear.jsUpload.msg.http_error',__('HTTP Error:')).
+		self::jsVar('dotclear.jsUpload.msg.error',__('Error:')).
+		self::jsVar('dotclear.jsUpload.msg.choose_file',__('Choose file')).
+		self::jsVar('dotclear.jsUpload.msg.choose_files',__('Choose files')).
+		self::jsVar('dotclear.jsUpload.msg.cancel',__('Cancel')).
+		self::jsVar('dotclear.jsUpload.msg.clean',__('Clean')).
+		self::jsVar('dotclear.jsUpload.msg.upload',__('Upload')).
+		self::jsVar('dotclear.jsUpload.msg.no_file_in_queue',__('No file in queue.')).
+		self::jsVar('dotclear.jsUpload.msg.file_in_queue',__('1 file in queue.')).
+		self::jsVar('dotclear.jsUpload.msg.files_in_queue',__('%d files in queue.')).
+		self::jsVar('dotclear.jsUpload.msg.queue_error',__('Queue error:')).
+		self::jsVar('dotclear.jsUpload.base_url',$base_url).
 		"\n//]]>\n".
 		"</script>\n";
Index: inc/admin/lib.pager.php
===================================================================
--- inc/admin/lib.pager.php	(revision 860)
+++ inc/admin/lib.pager.php	(revision 1146)
@@ -133,5 +133,5 @@
 		'<td class="nowrap">'.dt::dt2str(__('%Y-%m-%d %H:%M'),$this->rs->post_dt).'</td>'.
 		'<td class="nowrap">'.$cat_title.'</td>'.
-		'<td class="nowrap">'.$this->rs->user_id.'</td>'.
+		'<td class="nowrap">'.html::escapeHTML($this->rs->user_id).'</td>'.
 		'<td class="nowrap">'.$this->rs->nb_comment.'</td>'.
 		'<td class="nowrap">'.$this->rs->nb_trackback.'</td>'.
@@ -230,5 +230,5 @@
 		html::escapeHTML($this->rs->post_title).'</a></td>'.
 		'<td class="nowrap">'.dt::dt2str(__('%Y-%m-%d %H:%M'),$this->rs->post_dt).'</td>'.
-		'<td class="nowrap">'.$this->rs->user_id.'</td>'.
+		'<td class="nowrap">'.html::escapeHTML($this->rs->user_id).'</td>'.
 		'<td class="nowrap status">'.$img_status.' '.$selected.' '.$protected.' '.$attach.'</td>'.
 		'</tr>';
@@ -334,5 +334,5 @@
 		($this->rs->post_type != 'post' ? ' ('.html::escapeHTML($this->rs->post_type).')' : '').'</td>'.
 		'<td class="nowrap">'.dt::dt2str(__('%Y-%m-%d %H:%M'),$this->rs->comment_dt).'</td>'.
-		'<td class="nowrap"><a href="'.$author_url.'">'.$comment_author.'</a></td>'.
+		'<td class="nowrap"><a href="'.$author_url.'">'.html::escapeHTML($comment_author).'</a></td>'.
 		'<td class="nowrap">'.($this->rs->comment_trackback ? __('trackback') : __('comment')).'</td>'.
 		'<td class="nowrap status">'.$img_status.'</td>'.
@@ -410,7 +410,7 @@
 		'<td class="maximal"><a href="user.php?id='.$this->rs->user_id.'">'.
 		$this->rs->user_id.'</a>&nbsp;'.$img_status.'</td>'.
-		'<td class="nowrap">'.$this->rs->user_firstname.'</td>'.
-		'<td class="nowrap">'.$this->rs->user_name.'</td>'.
-		'<td class="nowrap">'.$this->rs->user_displayname.'</td>'.
+		'<td class="nowrap">'.html::escapeHTML($this->rs->user_firstname).'</td>'.
+		'<td class="nowrap">'.html::escapeHTML($this->rs->user_name).'</td>'.
+		'<td class="nowrap">'.html::escapeHTML($this->rs->user_displayname).'</td>'.
 		'<td class="nowrap"><a href="posts.php?user_id='.$this->rs->user_id.'">'.
 		$this->rs->nb_post.'</a></td>'.
