Index: branches/5.2.x/core/admin_templates/incs/form_blocks.tpl =================================================================== diff -u -N -r16415 -r16416 --- branches/5.2.x/core/admin_templates/incs/form_blocks.tpl (.../form_blocks.tpl) (revision 16415) +++ branches/5.2.x/core/admin_templates/incs/form_blocks.tpl (.../form_blocks.tpl) (revision 16416) @@ -343,8 +343,13 @@ -
-   + + + + + +
+
@@ -373,16 +378,6 @@ uploadURL : '', deleteURL : '', previewURL : '', - - // Button settings - buttonImageURL: 'img/upload.png', // Relative to the Flash file - buttonWidth: 63, - buttonHeight: 21, - buttonText: 'Browse', - buttonTextStyle: ".theFont { font-size: 12; font-family: arial, sans}", - buttonTextTopPadding: 2, - buttonTextLeftPadding: 9, - buttonPlaceholderId: '_place_holder', ajax: truefalse } ) Index: branches/5.2.x/core/admin_templates/img/upload.png =================================================================== diff -u -N -r13840 -r16416 Binary files differ Index: branches/5.2.x/index.php =================================================================== diff -u -N -r16415 -r16416 --- branches/5.2.x/index.php (.../index.php) (revision 16415) +++ branches/5.2.x/index.php (.../index.php) (revision 16416) @@ -1,6 +1,6 @@ = this.params.multiple) { - // new file can exceed allowed file number - if (this.params.multiple > 1) { - // it definetly exceed it - UploadsManager.onFileQueueError(file, -100, this.params.multiple); - this.callFlash('CancelUpload', [file.id]); - } - else { - // delete file added - this.files_count++; - this.files.push(file); - - if (this.files[0].uploaded) { - UploadsManager.DeleteFile(UploadsManager._getUploader(file).id, this.files[0].name, true); - } - else { - this.callFlash('CancelUpload', [this.files[0].id]); - } - - this.startUpload(); - } - } - else { - // new file will not exceed allowed file number - this.files_count++; - this.files.push(file); - - this.startUpload(); - } - - this.updateInfo(this.files.length - 1); - } - ) -}; - -UploadsManager.onUploadSuccess = function(file, serverData, receivedResponse) { - var $uploader = UploadsManager._getUploader(file); - - $uploader.queueEvent( - function() { - this.UploadSuccess(file, serverData, receivedResponse); - } - ); -}; - -UploadsManager.onUploadError = function(file, errorCode, message) { - var $uploader = UploadsManager._getUploader(file); - - $uploader.queueEvent( - function() { - this.removeFile(file); - - switch (errorCode) { - case -200: - // HTTP Error - message = parseInt(message); // HTTP Error Code - switch (message) { - case 403: - message = "You don't have permission to upload"; - break; - - case 413: - message = 'File size exceeds allowed limit'; - break; - - case 500: - message = 'Write permissions not set on the server, please contact server administrator'; - break; - } - - if (isNaN(message)) { - // message is processed - alert('Error: ' + message + "\n" + 'Occured on file ' + file.name); - return ; - } - break; - - case -280: - // File Cancelled - return ; - break; - - case -290: - // Upload Stopped - UploadsManager.UploadQueueComplete(this); - return ; - break; - } - - // all not processed error messages go here - alert('Error [' + errorCode + ']: ' + message + "\n" + 'Occured on file ' + file.name); - } - ); -}; - -UploadsManager.onFileQueueError = function(file, errorCode, message) { - switch (errorCode) { - case -100: - // maximal allowed file count reached - alert('Error: Files count exceeds allowed limit' + "\n" + 'Occured on file ' + file.name); - return ; - break; - - case -110: - // maximal allowed filesize reached - alert('Error: File size exceeds allowed limit' + "\n" + 'Occured on file ' + file.name); - return ; - break; - - case -130: - // maximal allowed filesize reached - alert('Error: File is not an allowed file type.' + "\n" + 'Occured on file ' + file.name); - return ; - break; - } - - // all not processed error messages go here - alert('Error [' + errorCode + ']: ' + message + "\n" + 'Occured on file ' + file.name); -}; - -UploadsManager.onFlashReady = function ($uploader_id) { - this._Uploaders[$uploader_id].onFlashReady(); +UploadsManager.onReady = function () { this._uploadersReady++; - if (this._uploadersReady == this._nextId) { + if (this._uploadersReady == this._uploadersCreated) { // all uploaders are ready Application.processHooks('m:OnUploadersReady'); } -}; - -UploadsManager.onDebug = function (message) { - if (!UploadsManager._debugMode) { - return ; - } - - var exceptionMessage, exceptionValues = []; - - // Check for an exception object and print it nicely - if (typeof(message) === 'object' && typeof(message.name) === 'string' && typeof(message.message) === 'string') { - for (var key in message) { - if (message.hasOwnProperty(key)) { - exceptionValues.push(key + ': ' + message[key]); - } - } - exceptionMessage = exceptionValues.join("\n") || ''; - exceptionValues = exceptionMessage.split("\n"); - exceptionMessage = 'EXCEPTION: ' + exceptionValues.join("\nEXCEPTION: "); - - console.log(exceptionMessage); - } else { - console.log(message); - } -}; - -if ( 'object' !== typeof console ) { - // emulate FireBug Console in other browsers to see flash debug messages - window.console = {}; - window.console.log = function (message) { - var console, documentForm; - - try { - console = document.getElementById('SWFUpload_Console'); - - if (!console) { - documentForm = document.createElement('form'); - document.getElementsByTagName('body')[0].appendChild(documentForm); - - console = document.createElement('textarea'); - console.id = 'SWFUpload_Console'; - console.style.fontFamily = 'monospace'; - console.setAttribute('wrap', 'off'); - console.wrap = 'off'; - console.style.overflow = 'auto'; - console.style.width = '700px'; - console.style.height = '350px'; - console.style.margin = '5px'; - documentForm.appendChild(console); - } - - console.value += message + "\n"; - - console.scrollTop = console.scrollHeight - console.clientHeight; - } catch (ex) { - alert('Exception: ' + ex.name + ' Message: ' + ex.message); - } - }; -} \ No newline at end of file +}; \ No newline at end of file Index: branches/5.2.x/core/units/helpers/upload_helper.php =================================================================== diff -u -N -r16002 -r16416 --- branches/5.2.x/core/units/helpers/upload_helper.php (.../5.3.x/core/units/helpers/upload_helper.php) (revision 16002) +++ branches/5.2.x/core/units/helpers/upload_helper.php (.../5.2.x/core/units/helpers/upload_helper.php) (revision 16416) @@ -61,9 +61,7 @@ throw new kUploaderException('File size exceeds allowed limit.', 413); } - if ( !$this->Application->isAdmin ) { - $value = array_map('htmlspecialchars_decode', $value); - } + $value = $this->Application->unescapeRequestVariable($value); $tmp_path = WRITEABLE . '/tmp/'; $filename = $this->getUploadedFilename() . '.tmp'; @@ -99,8 +97,11 @@ $this->deleteTempFiles($tmp_path); - if ( file_exists($tmp_path . 'resized/') ) { - $this->deleteTempFiles($tmp_path . 'resized/'); + $thumbs_path = preg_replace('/^' . preg_quote(FULL_PATH, '/') . '/', '', $tmp_path, 1); + $thumbs_path = FULL_PATH . THUMBS_PATH . $thumbs_path; + + if ( file_exists($thumbs_path) ) { + $this->deleteTempFiles($thumbs_path); } return preg_replace('/^' . preg_quote($id, '/') . '_/', '', $filename); @@ -194,13 +195,10 @@ */ protected function getStorageFormat($field_name, kEvent $event) { - $config = $event->getUnitConfig(); - $field_options = $config->getFieldByName($field_name); + $fields = $this->Application->getUnitOption($event->Prefix, 'Fields'); + $virtual_fields = $this->Application->getUnitOption($event->Prefix, 'VirtualFields'); + $field_options = array_key_exists($field_name, $fields) ? $fields[$field_name] : $virtual_fields[$field_name]; - if ( !$field_options ) { - $field_options = $config->getVirtualFieldByName($field_name); - } - return isset($field_options['storage_format']) ? $field_options['storage_format'] : false; } @@ -264,8 +262,8 @@ $files = glob($path . '*.*'); $max_file_date = strtotime('-1 day'); - foreach ($files as $file) { - if (filemtime($file) < $max_file_date) { + foreach ( $files as $file ) { + if ( filemtime($file) < $max_file_date ) { unlink($file); } } @@ -316,11 +314,8 @@ protected function getSafeFilename() { $filename = $this->Application->GetVar('file'); + $filename = $this->Application->unescapeRequestVariable($filename); - if ( !$this->Application->isAdmin ) { - $filename = htmlspecialchars_decode($filename); - } - if ( (strpos($filename, '../') !== false) || (trim($filename) !== $filename) ) { // when relative paths or special chars are found template names from url, then it's hacking attempt return false; Index: branches/5.2.x/core/admin_templates/js/uploader/uploader.js =================================================================== diff -u -N -r15858 -r16416 --- branches/5.2.x/core/admin_templates/js/uploader/uploader.js (.../uploader.js) (revision 15858) +++ branches/5.2.x/core/admin_templates/js/uploader/uploader.js (.../uploader.js) (revision 16416) @@ -1,9 +1,6 @@ -// this js class name is hardcoded in flash object :( -var SWFUpload = function () {}; -SWFUpload.instances = {}; - function Uploader(id, params) { this.id = id; + this.instance = null; // normalize params if (isNaN(parseInt(params.multiple))) { @@ -14,9 +11,7 @@ params.allowedFilesize = this._normalizeFilesize(params.allowedFilesize); // set params to uploader - this._eventQueue = []; - this.uploadCancelled = false; - this.flashReady = false; + this.ready = false; this.params = params; this._ensureDefaultValues(); @@ -29,6 +24,7 @@ this.deleteURL = params.deleteURL; this.enableUploadButton(); + this._fixFileExtensions(); this._attachEventHandler(); var $me = this; @@ -46,6 +42,10 @@ } /* ==== Private methods ==== */ +Uploader.prototype._fixFileExtensions = function() { + this.params.allowedFiletypes = this.params.allowedFiletypes.replace(/\*\./g, '').replace(/;/g, ','); +}; + Uploader.prototype._attachEventHandler = function() { var $me = this; @@ -56,46 +56,31 @@ Uploader.prototype._ensureDefaultValues = function() { // Upload backend settings - var $defaults = { - baseUrl : '', - uploadURL : '', - deleteURL : '', - previewURL : '', - useQueryString : false, - requeueOnError : false, - httpSuccess : '', - filePostName : 'Filedata', - allowedFiletypes : '*.*', - allowedFiletypesDescription : 'All Files', - allowedFilesize : 0, // Default zero means "unlimited" - multiple : 0, - field : '', - thumb_format: '', - urls : '', - names : '', - sizes : '', - fileQueueLimit : 0, - buttonImageURL : '', - buttonWidth : 1, - buttonHeight : 1, - buttonText : '', - buttonTextTopPadding : 0, - buttonTextLeftPadding : 0, - buttonTextStyle : 'color: #000000; font-size: 16pt;', - buttonAction : parseInt(this.params.multiple) == 1 ? -100 : -110, // SELECT_FILE : -100, SELECT_FILES : -110 - buttonDisabled : true, //false, - buttonCursor : -1, // ARROW : -1, HAND : -2 - wmode : 'transparent', // "window", "transparent", "opaque" - buttonPlaceholderId: false, - ajax: false - }; - for (var $param_name in $defaults) { - if (this.params[$param_name] == null) { -// console.log('setting default value [', $defaults[$param_name], '] for missing parameter [', $param_name, '] instead of [', this.params[$param_name], ']'); - this.params[$param_name] = $defaults[$param_name]; + var $me = this, + $defaults = { + baseUrl : '', + uploadURL : '', + deleteURL : '', + previewURL : '', + allowedFiletypes : '*.*', + allowedFiletypesDescription : 'All Files', + allowedFilesize : 0, // Default zero means "unlimited" + multiple : 0, + field : '', + thumb_format: '', + urls : '', + names : '', + sizes : '', + ajax: false + }; + + $.each($defaults, function ($param_name, $param_value) { + if ($me.params[$param_name] == null) { +// console.log('setting default value [', $param_value, '] for missing parameter [', $param_name, '] instead of [', $me.params[$param_name], ']'); + $me.params[$param_name] = $param_value; } - } + }); }; Uploader.prototype._normalizeFilesize = function($file_size) { @@ -109,8 +94,8 @@ }; Uploader.prototype._prepareFiles = function() { - var ids = ''; - var names = ''; + var ids = '', + names = ''; // process uploaded files for (var f = 0; f < this.files.length; f++) { @@ -122,8 +107,8 @@ names += this.files[f].name + '|'; } - ids = ids.replace(/\|$/, '', ids); - names = names.replace(/\|$/, '', names); + ids = ids.replace(/\|$/, ''); + names = names.replace(/\|$/, ''); document.getElementById(this.id+'[tmp_ids]').value = ids; document.getElementById(this.id+'[tmp_names]').value = names; @@ -142,71 +127,78 @@ return mb + ' MB'; }; -Uploader.prototype._executeNextEvent = function () { - var f = this._eventQueue ? this._eventQueue.shift() : null; - if (typeof(f) === 'function') { - f.apply(this); - } -}; - /* ==== Public methods ==== */ Uploader.prototype.init = function() { + var $me = this, + $uploader_options = { + runtimes : 'flash,html4', // html5 + chunk_size: '1mb', + browse_button : this.id + '_browse_button', + container: this.id + '_container', + url : this.params.uploadURL, + flash_swf_url : this.params.baseUrl + '/Moxie.swf', + multi_selection: this.params.multiple > 1, + filters : {}, + init: {} + }; + + if ( this.params.allowedFilesize > 0 ) { + $uploader_options.filters.max_file_size = this.params.allowedFilesize + 'kb'; + } + + if ( this.params.allowedFiletypes != '*' ) { + $uploader_options.filters.mime_types = [ + {title : this.params.allowedFiletypesDescription, extensions : this.params.allowedFiletypes} + ]; + } + this.IconPath = this.params.IconPath ? this.params.IconPath : '../admin_templates/img/browser/icons'; - // initialize flash object - this.flash_id = UploadsManager._nextFlashId(); + $uploader_options.init['Init'] = function(uploader) { + $me.onReady(); + }; - // add callbacks for every event, because none of callbacks will work in other case (see swfupload documentation) - SWFUpload.instances[this.flash_id] = this; - SWFUpload.instances[this.flash_id].flashReady = function () { UploadsManager.onFlashReady(this.id); }; - SWFUpload.instances[this.flash_id].fileDialogStart = UploadsManager.onHandleEverything; - SWFUpload.instances[this.flash_id].fileQueued = UploadsManager.onFileQueued; - SWFUpload.instances[this.flash_id].fileQueueError = UploadsManager.onFileQueueError; - SWFUpload.instances[this.flash_id].fileDialogComplete = UploadsManager.onHandleEverything; + $uploader_options.init['FilesAdded'] = function(uploader, files) { + $.each(files, function (index, file) { + $me.onFileQueued(file); + }); - SWFUpload.instances[this.flash_id].uploadStart = UploadsManager.onUploadStart; - SWFUpload.instances[this.flash_id].uploadProgress = UploadsManager.onUploadProgress; - SWFUpload.instances[this.flash_id].uploadError = UploadsManager.onUploadError; - SWFUpload.instances[this.flash_id].uploadSuccess = UploadsManager.onUploadSuccess; - SWFUpload.instances[this.flash_id].uploadComplete = UploadsManager.onUploadComplete; - SWFUpload.instances[this.flash_id].debug = UploadsManager.onDebug; + $me.startUpload(); + }; - this.swf = new SWFObject(this.params.baseUrl + '/swfupload.swf', this.flash_id, this.params.buttonWidth, this.params.buttonHeight, '9', '#FFFFFF'); - this.swf.setAttribute('style', ''); - this.swf.addParam('wmode', encodeURIComponent(this.params.wmode)); + $uploader_options.init['FilesRemoved'] = function(uploader, files) { + $.each(files, function (index, file) { + if ( file.status != plupload.QUEUED ) { + uploader.stop(); + uploader.start(); + } + }); + }; - this.swf.addVariable('movieName', encodeURIComponent(this.flash_id)); - this.swf.addVariable('fileUploadLimit', 0); - this.swf.addVariable('fileQueueLimit', encodeURIComponent(this.params.fileQueueLimit)); - this.swf.addVariable('fileSizeLimit', encodeURIComponent(this.params.allowedFilesize)); // in kilobytes - this.swf.addVariable('fileTypes', encodeURIComponent(this.params.allowedFiletypes)); - this.swf.addVariable('fileTypesDescription', encodeURIComponent(this.params.allowedFiletypesDescription)); - this.swf.addVariable('uploadURL', encodeURIComponent(this.params.uploadURL)); + $uploader_options.init['Error'] = function(uploader, error) { + $me.onError(error); + }; - // upload button appearance - this.swf.addVariable('buttonImageURL', encodeURIComponent(this.params.buttonImageURL)); - this.swf.addVariable('buttonWidth', encodeURIComponent(this.params.buttonWidth)); - this.swf.addVariable('buttonHeight', encodeURIComponent(this.params.buttonHeight)); - this.swf.addVariable('buttonText', encodeURIComponent(this.params.buttonText)); - this.swf.addVariable('buttonTextTopPadding', encodeURIComponent(this.params.buttonTextTopPadding)); - this.swf.addVariable('buttonTextLeftPadding', encodeURIComponent(this.params.buttonTextLeftPadding)); - this.swf.addVariable('buttonTextStyle', encodeURIComponent(this.params.buttonTextStyle)); - this.swf.addVariable('buttonAction', encodeURIComponent(this.params.buttonAction)); - this.swf.addVariable('buttonDisabled', encodeURIComponent(this.params.buttonDisabled)); - this.swf.addVariable('buttonCursor', encodeURIComponent(this.params.buttonCursor)); + $uploader_options.init['BeforeUpload'] = function(uploader, file) { + return $me.onUploadFileStart(uploader, file); + }; - if (UploadsManager._debugMode) { - this.swf.addVariable('debugEnabled', encodeURIComponent('true')); // flash var - } + $uploader_options.init['UploadProgress'] = function(uploader, file) { + $me.onUploadProgress(file); + }; - var $me = this; + $uploader_options.init['FileUploaded'] = function(uploader, file, response) { + $me.onUploadFinished(file, response); + }; + this.instance = new plupload.Uploader($uploader_options); + Application.setHook( 'm:OnAfterFormInit', function () { $me.renderBrowseButton(); } - ) + ); this.refreshQueue(); }; @@ -239,12 +231,15 @@ for (var i = 0; i < urls.length; i++) { var a_file = { + // original properties from Uploader id : this.getUploadedFileId(names[i]), name : names[i], - url : urls[i], size: sizes[i], - uploaded : 1, - progress: 100 + percent: 100, + + // custom properties + url : urls[i], + uploaded : 1 }; this.files_count++; @@ -278,67 +273,18 @@ }; Uploader.prototype.enableUploadButton = function() { - var $me = this; - - // enable upload button, when flash is fully loaded - this.queueEvent( - function() { - setTimeout( - function () { - $me.callFlash('SetButtonDisabled', [false]); - }, 0 - ) - } - ); + // enable upload button, when plupload runtime is fully loaded + $('#' + jq(this.id + '_browse_button')).prop('disabled', false).removeClass('button-disabled'); }; Uploader.prototype.renderBrowseButton = function() { - var holder = document.getElementById(this.params.buttonPlaceholderId); - this.swf.write(holder); - - this.flash = document.getElementById(this.flash_id); + this.instance.init(); }; Uploader.prototype.remove = function() { - var id = this.params.buttonPlaceholderId; - - var obj = document.getElementById(id); - - if (obj/* && obj.nodeName == "OBJECT"*/) { - var u = navigator.userAgent.toLowerCase(); - var p = navigator.platform.toLowerCase(); - var windows = p ? /win/.test(p) : /win/.test(u); - var $me = this; - - if (document.all && windows) { - obj.style.display = "none"; - (function(){ - if (obj.readyState == 4) { - $me.removeObjectInIE(id); - } - else { - setTimeout(arguments.callee, 10); - } - })(); - } - else { - obj.parentNode.removeChild(obj); - } - } + this.instance.destroy(); }; -Uploader.prototype.removeObjectInIE = function(id) { - var obj = document.getElementById(id); - if (obj) { - for (var i in obj) { - if (typeof obj[i] == 'function') { - obj[i] = null; - } - } - obj.parentNode.removeChild(obj); - } -}; - Uploader.prototype.isImage = function($filename) { this.removeTempExtension($filename).match(/\.([^.]*)$/); @@ -379,10 +325,11 @@ }; Uploader.prototype.getQueueElement = function($file) { - var $ret = ''; - var $icon_image = this.getFileIcon($file.name); - var $file_label = this.removeTempExtension($file.name) + ' (' + this._formatSize($file.size) + ')'; - var $need_preview = false; + var $me = this, + $ret = '', + $icon_image = this.getFileIcon($file.name), + $file_label = this.removeTempExtension($file.name) + ' (' + this._formatSize($file.size) + ')', + $need_preview = false; if (isset($file.uploaded)) { // add deletion checkbox @@ -393,7 +340,7 @@ $ret += '
'; if ($need_preview) { - $ret += ''; + $ret += ''; } else { $ret += ''; @@ -422,20 +369,15 @@ $ret = $('
' + $ret + '
'); // set click events - var $me = this; + $('.delete-file-btn', $ret).click(function ($e) { + $(this).prop('checked', !$me.deleteFile($file)); + }); - $('.delete-file-btn', $ret).click( - function ($e) { - $(this).prop('checked', !UploadsManager.DeleteFile($me.id, $file.name)); - } - ); + $('.cancel-upload-btn', $ret).click(function ($e) { + $me.removeFile($file); - $('.cancel-upload-btn', $ret).click( - function ($e) { - UploadsManager.CancelFile(UploadsManager._getUploader($file).id, $file.id); - return false; - } - ); + $e.preventDefault(); + }); // prepare auto-loading preview var $image = $('img.thumbnail-image', $ret); @@ -509,7 +451,7 @@ }; Uploader.prototype.updateProgressOnly = function ($file_index) { - var $progress_code = '
'; + var $progress_code = '
'; $('#' + this.files[$file_index].id + '_progress').html($progress_code); }; @@ -519,15 +461,19 @@ n_files = [], $to_delete = []; - for (var f = 0; f < this.files.length; f++) { - if (this.files[f].id != file.id && this.files[f].name != file.id) { - n_files.push(this.files[f]); - count++; + if (!isset(file.uploaded)) { + this.instance.removeFile(file); + } + + $.each(this.files, function (f, current_file) { + if ( current_file.id == file.id || current_file.name == file.name ) { + $to_delete.push(f); } else { - $to_delete.push(f); + n_files.push(current_file); + count++; } - } + }); for (var $i = 0; $i < $to_delete.length; $i++) { this.updateQueueFile($to_delete[$i], true); @@ -551,206 +497,192 @@ }; Uploader.prototype.startUpload = function() { - this.uploadCancelled = false; - - if (!this.hasQueue()) { - return; + if ( this.hasQueue() ) { + this.instance.start(); } - - this.callFlash('StartUpload'); }; -Uploader.prototype.cancelUpload = function() { - this.callFlash('StopUpload'); - var $stats = this.callFlash('GetStats'); - - while ($stats.files_queued > 0) { - this.callFlash('CancelUpload'); - $stats = this.callFlash('GetStats'); +Uploader.prototype.deleteFile = function(file, confirmed) { + if (!confirmed && !confirm('Are you sure you want to delete "' + file.name + '" file?')) { + return false; } - this.uploadCancelled = true; + var $me = this; + + $.get( + this.getDeleteUrl(file), + function ($data) { + $me.removeFile(file); + $me.deleted.push(file.name); + $me.updateInfo(undefined, true); + } + ); + + return true; }; -Uploader.prototype.UploadFileStart = function(file) { - var $file_index = this.getFileIndex(file); - this.files[$file_index].progress = 0; +Uploader.prototype.onUploadFileStart = function(uploader, file) { + var $upload_url = this.params.uploadURL, + $file_index = this.getFileIndex(file), + $extra_params = { + field: this.params.field, + id: file.id, + flashsid: this.params.flashsid + }; + + this.files[$file_index].percent = file.percent; this.updateProgressOnly($file_index); - this.callFlash('AddFileParam', [file.id, 'field', this.params.field]); - this.callFlash('AddFileParam', [file.id, 'id', file.id]); - this.callFlash('AddFileParam', [file.id, 'flashsid', this.params.flashsid]); + $upload_url += ($upload_url.indexOf('?') ? '&' : '?'); - // we can prevent user from adding any files here :) - this.callFlash('ReturnUploadStart', [true]); + $.each($extra_params, function ($param_name, $param_value) { + $upload_url += $param_name + '=' + encodeURIComponent($param_value) + '&'; + }); + + uploader.settings.url = $upload_url; + + return true; }; -Uploader.prototype.UploadProgress = function(file, bytesLoaded, bytesTotal) { +Uploader.prototype.onUploadProgress = function(file) { var $file_index = this.getFileIndex(file); - this.files[$file_index].progress = Math.round(bytesLoaded / bytesTotal * 100); + + this.files[$file_index].percent = file.percent; this.updateProgressOnly($file_index); }; -Uploader.prototype.UploadSuccess = function(file, serverData, receivedResponse) { - if (!receivedResponse) { - return ; - } +Uploader.prototype.onFileQueued = function(file) { + if (this.files_count >= this.params.multiple) { + // new file can exceed allowed file number + if (this.params.multiple > 1) { + // it definitely exceed it + var $error = { + 'file': file, 'code': 'ERROR_1', 'message': 'Files count exceeds allowed limit.' + }; - for (var f = 0; f < this.files.length; f++) { - if (this.files[f].id == file.id) { - // new uploaded file name returned by OnUploadFile event - this.files[f].name = serverData; + this.instance.trigger('Error', $error); } + else { + // delete file added + this.files_count++; + this.files.push(file); + + if (this.files[0].uploaded) { + this.deleteFile(this.files[0], true); + } + else { + this.instance.removeFile(file); + } + } } + else { + // new file will not exceed allowed file number + this.files_count++; + this.files.push(file); + } + + this.updateInfo(this.files.length - 1); }; -Uploader.prototype.UploadFileComplete = function(file) { - // file was uploaded OR file upload was cancelled - var $file_index = this.getFileIndex(file); +Uploader.prototype.onError = function(error) { + this.removeFile(error.file); - if ($file_index !== false) { - // in case if file upload was cancelled, then no info here - this.files[$file_index].uploaded = 1; - this.files[$file_index].progress = 100; - this.files[$file_index].temp = 1; - this.files[$file_index].url = this.getUrl(this.files[$file_index]); - this.updateInfo($file_index); + if ( error.code == plupload.FILE_SIZE_ERROR ) { + error.message = 'File size exceeds allowed limit.'; } - - // upload next file in queue - var $stats = this.callFlash('GetStats'); - - if ($stats.files_queued > 0) { - this.callFlash('StartUpload'); + else if ( error.code == plupload.FILE_EXTENSION_ERROR ) { + error.message = 'File is not an allowed file type.'; } - else { - UploadsManager.UploadQueueComplete(this); - } + + setTimeout(function () { + alert('Error: ' + error.message + "\n" + 'Occurred on file ' + error.file.name); + }, 0); }; -Uploader.prototype.getUrl = function($file, $preview) { - var $url = this.params.previewURL.replace('#FILE#', encodeURIComponent($file.name)).replace('#FIELD#', this.params.field); +Uploader.prototype.onUploadFinished = function(file, response) { + var $json_response = eval('(' + response.response + ')'); - if ( $file.temp !== undefined && $file.temp ) { - $url += '&tmp=1&id=' + $file.id; + if (response.status != 200) { + return ; } - if ( $preview !== undefined && $preview === true ) { - $url += '&thumb=1'; - } + if ( $json_response.status == 'error' ) { + var $error = { + 'file': file, 'code': $json_response.error.code, 'message': $json_response.error.message + }; - return $url; -}; + this.instance.trigger('Error', $error); -Uploader.prototype.getFileIndex = function(file) { - for (var f = 0; f < this.files.length; f++) { - if (this.files[f].id == file.id) { - return f; - } + return ; } - return false; + // new uploaded file name returned by OnUploadFile event + file.name = $json_response.result; + + this.onUploadFileComplete(file); }; -Uploader.prototype.queueEvent = function (function_body) { - // Warning: Don't call this.debug inside here or you'll create an infinite loop - var self = this; +Uploader.prototype.onUploadFileComplete = function(file) { + // file was uploaded OR file upload was cancelled + var $file_index = this.getFileIndex(file); - // Queue the event - this._eventQueue.push(function_body); + if ($file_index !== false) { + // in case if file upload was cancelled, then no info here + this.files[$file_index].name = file.name; + this.files[$file_index].percent = file.percent; - if (!this.flashReady) { - // don't execute any flash-related events, while it's not completely loaded - return ; + this.files[$file_index].temp = 1; + this.files[$file_index].uploaded = 1; + this.files[$file_index].url = this.getPreviewUrl(this.files[$file_index]); + this.updateInfo($file_index); } - - // Execute the next queued event - setTimeout( - function () { - self._executeNextEvent(); - }, 0 - ); }; -Uploader.prototype._executeQueuedEvents = function() { - var $me = this; +Uploader.prototype.getPreviewUrl = function($file, $preview) { + var $url = this.getUrl(this.params.previewURL, $file); - setTimeout( - function () { - $me._executeNextEvent(); + if ( $preview !== undefined && $preview === true ) { + $url += '&thumb=1'; + } - if ($me._eventQueue.length > 0) { - $me._executeQueuedEvents(); - } + return $url; +}; - }, 0 - ); +Uploader.prototype.getDeleteUrl = function($file) { + return this.getUrl(this.params.deleteURL, $file); }; -// Private: callFlash handles function calls made to the Flash element. -// Calls are made with a setTimeout for some functions to work around -// bugs in the ExternalInterface library. -Uploader.prototype.callFlash = function (functionName, argumentArray) { - argumentArray = argumentArray || []; +Uploader.prototype.getUrl = function($base_url, $file) { + var $replacements = { + '#FILE#': $file.name, + '#FIELD#': this.params.field, + '#FIELD_ID#': this.id + }; - var returnValue; + var $url = $base_url; - if (typeof this.flash[functionName] === 'function') { - // We have to go through all this if/else stuff because the Flash functions don't have apply() and only accept the exact number of arguments. - if (argumentArray.length === 0) { - returnValue = this.flash[functionName](); - } else if (argumentArray.length === 1) { - returnValue = this.flash[functionName](argumentArray[0]); - } else if (argumentArray.length === 2) { - returnValue = this.flash[functionName](argumentArray[0], argumentArray[1]); - } else if (argumentArray.length === 3) { - returnValue = this.flash[functionName](argumentArray[0], argumentArray[1], argumentArray[2]); - } else { - throw 'Too many arguments'; - } + $.each($replacements, function ($replace_from, $replace_to) { + $url = $url.replace($replace_from, encodeURIComponent($replace_to)); + }); - // Unescape file post param values - if (returnValue != undefined && typeof returnValue.post === 'object') { - returnValue = this.unescapeFilePostParams(returnValue); - } - - return returnValue; - } else { -// alert('invalid function name: ' + functionName); - throw "Invalid function name: " + functionName; + if ( $file.temp !== undefined && $file.temp ) { + $url += '&tmp=1&id=' + $file.id; } + + return $url; }; -// Private: unescapeFileParams is part of a workaround for a flash bug where objects passed through ExternalInterface cannot have -// properties that contain characters that are not valid for JavaScript identifiers. To work around this -// the Flash Component escapes the parameter names and we must unescape again before passing them along. -Uploader.prototype.unescapeFilePostParams = function (file) { - var reg = /[$]([0-9a-f]{4})/i; - var unescapedPost = {}; - var uk; - - if (file != undefined) { - for (var k in file.post) { - if (file.post.hasOwnProperty(k)) { - uk = k; - var match; - while ((match = reg.exec(uk)) !== null) { - uk = uk.replace(match[0], String.fromCharCode(parseInt("0x" + match[1], 16))); - } - unescapedPost[uk] = file.post[k]; - } +Uploader.prototype.getFileIndex = function(file) { + for (var f = 0; f < this.files.length; f++) { + if (this.files[f].id == file.id) { + return f; } - - file.post = unescapedPost; } - return file; + return false; }; -Uploader.prototype.onFlashReady = function() { - var $me = this; - this.flashReady = true; - - // process events, queued before flash load - this._executeQueuedEvents(); +Uploader.prototype.onReady = function() { + this.ready = true; + UploadsManager.onReady(); }; \ No newline at end of file Index: branches/5.2.x/core/kernel/db/db_event_handler.php =================================================================== diff -u -N -r16415 -r16416 --- branches/5.2.x/core/kernel/db/db_event_handler.php (.../db_event_handler.php) (revision 16415) +++ branches/5.2.x/core/kernel/db/db_event_handler.php (.../db_event_handler.php) (revision 16416) @@ -1,6 +1,6 @@ status = kEvent::erSTOP; -// define('DBG_SKIP_REPORTING', 0); - $default_msg = "Flash requires that we output something or it won't fire the uploadSuccess event"; - if ( !$this->Application->HttpQuery->Post ) { - // Variables {field, id, flashsid} are always submitted through POST! - // When file size is larger, then "upload_max_filesize" (in php.ini), - // then these variables also are not submitted -> handle such case. - header('HTTP/1.0 413 File size exceeds allowed limit'); - echo $default_msg; - return; - } + /** @var kUploadHelper $upload_helper */ + $upload_helper = $this->Application->recallObject('kUploadHelper'); - if ( !$this->_checkFlashUploaderPermission($event) ) { - // 403 Forbidden - header('HTTP/1.0 403 You don\'t have permissions to upload'); - echo $default_msg; - return; - } + try { + $filename = $upload_helper->handle($event); - $value = $this->Application->GetVar('Filedata'); - - if ( !$value || ($value['error'] != UPLOAD_ERR_OK) ) { - // 413 Request Entity Too Large (file uploads disabled OR uploaded file was - // to large for web server to accept, see "upload_max_filesize" in php.ini) - header('HTTP/1.0 413 File size exceeds allowed limit'); - echo $default_msg; - return; + $response = array( + 'jsonrpc' => '2.0', + 'status' => 'success', + 'result' => $filename, + ); } - - $value = $this->Application->HttpQuery->unescapeRequestVariable($value); - - $tmp_path = WRITEABLE . '/tmp/'; - $filename = $value['name'] . '.tmp'; - $id = $this->Application->GetVar('id'); - - if ( $id ) { - $filename = $id . '_' . $filename; + catch ( kUploaderException $e ) { + $response = array( + 'jsonrpc' => '2.0', + 'status' => 'error', + 'error' => array('code' => $e->getCode(), 'message' => $e->getMessage()), + ); } - if ( !is_writable($tmp_path) ) { - // 500 Internal Server Error - // check both temp and live upload directory - header('HTTP/1.0 500 Write permissions not set on the server'); - echo $default_msg; - return; - } - - $file_helper = $this->Application->recallObject('FileHelper'); - /* @var $file_helper FileHelper */ - - $filename = $file_helper->ensureUniqueFilename($tmp_path, $filename); - $storage_format = $this->_getStorageFormat($this->Application->GetVar('field'), $event); - - if ( $storage_format ) { - $image_helper = $this->Application->recallObject('ImageHelper'); - /* @var $image_helper ImageHelper */ - - move_uploaded_file($value['tmp_name'], $value['tmp_name'] . '.jpg'); // add extension, so ResizeImage can work - $url = $image_helper->ResizeImage($value['tmp_name'] . '.jpg', $storage_format); - $tmp_name = preg_replace('/^' . preg_quote($this->Application->BaseURL(), '/') . '/', '/', $url); - rename($tmp_name, $tmp_path . $filename); - } - else { - move_uploaded_file($value['tmp_name'], $tmp_path . $filename); - } - - echo preg_replace('/^' . preg_quote($id, '/') . '_/', '', $filename); - - $this->deleteTempFiles($tmp_path); - - $thumbs_path = preg_replace('/^' . preg_quote(FULL_PATH, '/') . '/', '', $tmp_path, 1); - $thumbs_path = FULL_PATH . THUMBS_PATH . $thumbs_path; - - if ( file_exists($thumbs_path) ) { - $this->deleteTempFiles($thumbs_path); - } + echo json_encode($response); } /** - * Gets storage format for a given field - * - * @param string $field_name - * @param kEvent $event - * @return bool - * @access protected - */ - protected function _getStorageFormat($field_name, kEvent $event) - { - $fields = $this->Application->getUnitOption($event->Prefix, 'Fields'); - $virtual_fields = $this->Application->getUnitOption($event->Prefix, 'VirtualFields'); - $field_options = array_key_exists($field_name, $fields) ? $fields[$field_name] : $virtual_fields[$field_name]; - - return isset($field_options['storage_format']) ? $field_options['storage_format'] : false; - } - - /** - * Delete temporary files, that won't be used for sure - * - * @param string $path - * @return void - * @access protected - */ - protected function deleteTempFiles($path) - { - $files = glob($path . '*.*'); - $max_file_date = strtotime('-1 day'); - - foreach ( $files as $file ) { - if ( filemtime($file) < $max_file_date ) { - unlink($file); - } - } - } - - /** - * Checks, that flash uploader is allowed to perform upload - * - * @param kEvent $event - * @return bool - */ - protected function _checkFlashUploaderPermission(kEvent $event) - { - // Flash uploader does NOT send correct cookies, so we need to make our own check - $cookie_name = 'adm_' . $this->Application->ConfigValue('SessionCookieName'); - $this->Application->HttpQuery->Cookie['cookies_on'] = 1; - $this->Application->HttpQuery->Cookie[$cookie_name] = $this->Application->GetVar('flashsid'); - - // this prevents session from auto-expiring when KeepSessionOnBrowserClose & FireFox is used - $this->Application->HttpQuery->Cookie[$cookie_name . '_live'] = $this->Application->GetVar('flashsid'); - - $admin_ses = $this->Application->recallObject('Session.admin'); - /* @var $admin_ses Session */ - - if ( $admin_ses->RecallVar('user_id') == USER_ROOT ) { - return true; - } - - // copy some data from given session to current session - $backup_user_id = $this->Application->RecallVar('user_id'); - $this->Application->StoreVar('user_id', $admin_ses->RecallVar('user_id')); - - $backup_user_groups = $this->Application->RecallVar('UserGroups'); - $this->Application->StoreVar('UserGroups', $admin_ses->RecallVar('UserGroups')); - - // check permissions using event, that have "add|edit" rule - $check_event = new kEvent($event->getPrefixSpecial() . ':OnProcessSelected'); - $check_event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true)); - $allowed_to_upload = $this->CheckPermission($check_event); - - // restore changed data, so nothing gets saved to database - $this->Application->StoreVar('user_id', $backup_user_id); - $this->Application->StoreVar('UserGroups', $backup_user_groups); - - return $allowed_to_upload; - } - - /** * Remembers, that file should be deleted on item's save from temp table * * @param kEvent $event @@ -3291,28 +3159,30 @@ protected function OnDeleteFile(kEvent $event) { $event->status = kEvent::erSTOP; - $filename = $this->_getSafeFilename(); + $field_id = $this->Application->GetVar('field_id'); - if ( !$filename ) { + if ( !preg_match_all('/\[([^\[\]]*)\]/', $field_id, $regs) ) { return; } - $object = $event->getObject(Array ('skip_autoload' => true)); - /* @var $object kDBItem */ + $field = $regs[1][1]; + $record_id = $regs[1][0]; - $field_id = $this->Application->GetVar('field_id'); + /** @var kUploadHelper $upload_helper */ + $upload_helper = $this->Application->recallObject('kUploadHelper'); + $object = $upload_helper->prepareUploadedFile($event, $field); - if ( !preg_match_all('/\[([^\[\]]*)\]/', $field_id, $regs) ) { + if ( !$object->GetDBField($field) ) { return; } - $field = $regs[1][1]; - $record_id = $regs[1][0]; $pending_actions = $object->getPendingActions($record_id); - $upload_dir = $object->GetFieldOption($field, 'upload_dir'); $pending_actions[] = Array ( - 'action' => 'delete', 'id' => $record_id, 'field' => $field, 'file' => FULL_PATH . $upload_dir . $filename + 'action' => 'delete', + 'id' => $record_id, + 'field' => $field, + 'file' => $object->GetField($field, 'full_path'), ); $object->setPendingActions($pending_actions, $record_id); @@ -3328,41 +3198,26 @@ protected function OnViewFile(kEvent $event) { $event->status = kEvent::erSTOP; - $filename = $this->_getSafeFilename(); - - if ( !$filename ) { - return; - } - - $object = $event->getObject(Array ('skip_autoload' => true)); - /* @var $object kDBItem */ - $field = $this->Application->GetVar('field'); - $options = $object->GetFieldOptions($field); - // set current uploaded file - if ( $this->Application->GetVar('tmp') ) { - $options['upload_dir'] = WRITEBALE_BASE . '/tmp/'; - unset($options['include_path']); - $object->SetFieldOptions($field, $options); + /** @var kUploadHelper $upload_helper */ + $upload_helper = $this->Application->recallObject('kUploadHelper'); + $object = $upload_helper->prepareUploadedFile($event, $field); - $object->SetDBField($field, $this->Application->GetVar('id') . '_' . $filename); + if ( !$object->GetDBField($field) ) { + return; } - else { - $object->SetDBField($field, $filename); - } // get url to uploaded file if ( $this->Application->GetVar('thumb') ) { - $url = $object->GetField($field, $options['thumb_format']); + $url = $object->GetField($field, $object->GetFieldOption($field, 'thumb_format')); } else { $url = $object->GetField($field, 'raw_url'); } + /** @var FileHelper $file_helper */ $file_helper = $this->Application->recallObject('FileHelper'); - /* @var $file_helper FileHelper */ - $path = $file_helper->urlToPath($url); if ( !file_exists($path) ) { @@ -3371,31 +3226,12 @@ header('Content-Length: ' . filesize($path)); $this->Application->setContentType(kUtil::mimeContentType($path), false); - header('Content-Disposition: inline; filename="' . kUtil::removeTempExtension($filename) . '"'); + header('Content-Disposition: inline; filename="' . kUtil::removeTempExtension($object->GetDBField($field)) . '"'); readfile($path); } /** - * Returns safe version of filename specified in url - * - * @return bool|string - * @access protected - */ - protected function _getSafeFilename() - { - $filename = $this->Application->GetVar('file'); - $filename = $this->Application->unescapeRequestVariable($filename); - - if ( (strpos($filename, '../') !== false) || (trim($filename) !== $filename) ) { - // when relative paths or special chars are found template names from url, then it's hacking attempt - return false; - } - - return $filename; - } - - /** * Validates MInput control fields * * @param kEvent $event Index: branches/5.2.x/core/units/helpers/helpers_config.php =================================================================== diff -u -N -r16377 -r16416 --- branches/5.2.x/core/units/helpers/helpers_config.php (.../helpers_config.php) (revision 16377) +++ branches/5.2.x/core/units/helpers/helpers_config.php (.../helpers_config.php) (revision 16416) @@ -1,6 +1,6 @@ 'BackupHelper', 'class' => 'BackupHelper', 'file' => 'backup_helper.php', 'build_event' => ''), Array ('pseudo' => 'AjaxFormHelper', 'class' => 'AjaxFormHelper', 'file' => 'ajax_form_helper.php', 'build_event' => ''), Array ('pseudo' => 'kCronHelper', 'class' => 'kCronHelper', 'file' => 'cron_helper.php', 'build_event' => ''), + Array ('pseudo' => 'kUploadHelper', 'class' => 'kUploadHelper', 'file' => 'upload_helper.php', 'build_event' => ''), ), );