Better handling of max file upload size according to PHP settings

- Allow unlimited size
- Better parsing of PHP size

Fixes #4896
This commit is contained in:
Frédéric Guillot 2023-03-02 20:24:12 -08:00 committed by Frédéric Guillot
parent 6e84f41517
commit b138a99ce3
11 changed files with 56 additions and 31 deletions

View File

@ -21,7 +21,7 @@ class ProjectFileController extends BaseController
$this->response->html($this->template->render('project_file/create', array( $this->response->html($this->template->render('project_file/create', array(
'project' => $project, 'project' => $project,
'max_size' => $this->helper->text->phpToBytes(get_upload_max_size()), 'max_size' => get_upload_max_size(),
))); )));
} }

View File

@ -40,7 +40,7 @@ class TaskFileController extends BaseController
$this->response->html($this->template->render('task_file/create', array( $this->response->html($this->template->render('task_file/create', array(
'task' => $task, 'task' => $task,
'max_size' => $this->helper->text->phpToBytes(get_upload_max_size()), 'max_size' => get_upload_max_size(),
))); )));
} }

View File

@ -71,29 +71,6 @@ class TextHelper extends Base
return round(pow(1024, $base - floor($base)), $precision).$suffixes[(int)floor($base)]; return round(pow(1024, $base - floor($base)), $precision).$suffixes[(int)floor($base)];
} }
/**
* Get the number of bytes from PHP size
*
* @param integer $val PHP size (example: 2M)
* @return integer
*/
public function phpToBytes($val)
{
$size = (int) substr($val, 0, -1);
$last = strtolower(substr($val, -1));
switch ($last) {
case 'g':
$size *= 1024;
case 'm':
$size *= 1024;
case 'k':
$size *= 1024;
}
return $size;
}
/** /**
* Return true if needle is contained in the haystack * Return true if needle is contained in the haystack
* *

View File

@ -9,7 +9,7 @@
'labelDropzone' => t('Drag and drop your files here'), 'labelDropzone' => t('Drag and drop your files here'),
'labelOr' => t('or'), 'labelOr' => t('or'),
'labelChooseFiles' => t('choose files'), 'labelChooseFiles' => t('choose files'),
'labelOversize' => t('The maximum allowed file size is %sB.', $this->text->bytes($max_size)), 'labelOversize' => $max_size > 0 ? t('The maximum allowed file size is %sB.', $this->text->bytes($max_size)) : null,
'labelSuccess' => t('All files have been uploaded successfully.'), 'labelSuccess' => t('All files have been uploaded successfully.'),
'labelCloseSuccess' => t('Close this window'), 'labelCloseSuccess' => t('Close this window'),
'labelUploadError' => t('Unable to upload this file.'), 'labelUploadError' => t('Unable to upload this file.'),

View File

@ -9,7 +9,7 @@
'labelDropzone' => t('Drag and drop your files here'), 'labelDropzone' => t('Drag and drop your files here'),
'labelOr' => t('or'), 'labelOr' => t('or'),
'labelChooseFiles' => t('choose files'), 'labelChooseFiles' => t('choose files'),
'labelOversize' => t('The maximum allowed file size is %sB.', $this->text->bytes($max_size)), 'labelOversize' => $max_size > 0 ? t('The maximum allowed file size is %sB.', $this->text->bytes($max_size)) : null,
'labelSuccess' => t('All files have been uploaded successfully.'), 'labelSuccess' => t('All files have been uploaded successfully.'),
'labelCloseSuccess' => t('Close this window'), 'labelCloseSuccess' => t('Close this window'),
'labelUploadError' => t('Unable to upload this file.'), 'labelUploadError' => t('Unable to upload this file.'),

View File

@ -13,7 +13,9 @@
<?= $this->form->label(t('CSV File'), 'file') ?> <?= $this->form->label(t('CSV File'), 'file') ?>
<?= $this->form->file('file', $errors) ?> <?= $this->form->file('file', $errors) ?>
<?php if ($max_size > 0): ?>
<p class="form-help"><?= t('Maximum size: ') ?><?= is_integer($max_size) ? $this->text->bytes($max_size) : $max_size ?></p> <p class="form-help"><?= t('Maximum size: ') ?><?= is_integer($max_size) ? $this->text->bytes($max_size) : $max_size ?></p>
<?php endif ?>
<?= $this->modal->submitButtons(array('submitLabel' => t('Import'))) ?> <?= $this->modal->submitButtons(array('submitLabel' => t('Import'))) ?>
</form> </form>

View File

@ -30,7 +30,9 @@
<?= $this->form->label(t('CSV File'), 'file') ?> <?= $this->form->label(t('CSV File'), 'file') ?>
<?= $this->form->file('file', $errors) ?> <?= $this->form->file('file', $errors) ?>
<?php if ($max_size > 0): ?>
<p class="form-help"><?= t('Maximum size: ') ?><?= is_integer($max_size) ? $this->text->bytes($max_size) : $max_size ?></p> <p class="form-help"><?= t('Maximum size: ') ?><?= is_integer($max_size) ? $this->text->bytes($max_size) : $max_size ?></p>
<?php endif ?>
<?= $this->modal->submitButtons(array('submitLabel' => t('Import'))) ?> <?= $this->modal->submitButtons(array('submitLabel' => t('Import'))) ?>
</form> </form>

View File

@ -212,7 +212,44 @@ function build_app_version($ref, $commit_hash)
*/ */
function get_upload_max_size() function get_upload_max_size()
{ {
return min(ini_get('upload_max_filesize'), ini_get('post_max_size')); $upload_max_filesize = convert_php_size_to_bytes(ini_get('upload_max_filesize'));
$post_max_size = convert_php_size_to_bytes(ini_get('post_max_size'));
if ($post_max_size == 0) {
return $upload_max_filesize;
}
if ($upload_max_filesize == 0) {
return $post_max_size;
}
return min($post_max_size, $upload_max_filesize);
}
/**
* Get the number of bytes from PHP size
*
* @param integer $value PHP size (example: 2M)
* @return integer
*/
function convert_php_size_to_bytes($value)
{
// Remove the non-unit characters from the size
$unit = preg_replace('/[^bkmgtpezy]/i', '', $value);
// Remove the non-numeric characters from the size
$size = preg_replace('/[^0-9\.]/', '', $value);
switch (strtoupper($unit)) {
case 'G':
$size *= 1024;
case 'M':
$size *= 1024;
case 'K':
$size *= 1024;
}
return $size;
} }
/** /**

View File

@ -114,7 +114,7 @@ function showFiles(){if(files.length>0){KB.trigger('modal.enable');KB.dom(dropzo
function buildFileInputElement(){return KB.dom('input').attr('id','file-input-element').attr('type','file').attr('name','files[]').attr('multiple',!0).on('change',onFileChange).hide().build()} function buildFileInputElement(){return KB.dom('input').attr('id','file-input-element').attr('type','file').attr('name','files[]').attr('multiple',!0).on('change',onFileChange).hide().build()}
function buildInnerDropzoneElement(){var dropzoneLinkElement=KB.dom('a').attr('href','#').text(options.labelChooseFiles).click(onClickFileBrowser).build();return KB.dom('div').attr('id','file-dropzone-inner').text(options.labelDropzone+' '+options.labelOr+' ').add(dropzoneLinkElement).build()} function buildInnerDropzoneElement(){var dropzoneLinkElement=KB.dom('a').attr('href','#').text(options.labelChooseFiles).click(onClickFileBrowser).build();return KB.dom('div').attr('id','file-dropzone-inner').text(options.labelDropzone+' '+options.labelOr+' ').add(dropzoneLinkElement).build()}
function buildDropzoneElement(){var dropzoneElement=KB.dom('div').attr('id','file-dropzone').add(buildInnerDropzoneElement()).build();dropzoneElement.ondragover=onDragOver;dropzoneElement.ondrop=onDrop;dropzoneElement.ondragover=onDragOver;return dropzoneElement} function buildDropzoneElement(){var dropzoneElement=KB.dom('div').attr('id','file-dropzone').add(buildInnerDropzoneElement()).build();dropzoneElement.ondragover=onDragOver;dropzoneElement.ondrop=onDrop;dropzoneElement.ondragover=onDragOver;return dropzoneElement}
function buildFileListItem(index){var isOversize=!1;var progressElement=KB.dom('progress').attr('id','file-progress-'+index).attr('value',0).build();var percentageElement=KB.dom('span').attr('id','file-percentage-'+index).text('(0%)').build();var deleteElement=KB.dom('span').attr('id','file-delete-'+index).html('<a href="#"><i class="fa fa-trash fa-fw"></i></a>').on('click',function(){files.splice(index,1);KB.find('#file-item-'+index).remove();showFiles()}).build();var itemElement=KB.dom('li').attr('id','file-item-'+index).add(deleteElement).add(progressElement).text(' '+files[index].name+' ').add(percentageElement);if(files[index].size>options.maxSize){itemElement.add(KB.dom('div').addClass('file-error').text(options.labelOversize).build());isOversize=!0} function buildFileListItem(index){var isOversize=!1;var progressElement=KB.dom('progress').attr('id','file-progress-'+index).attr('value',0).build();var percentageElement=KB.dom('span').attr('id','file-percentage-'+index).text('(0%)').build();var deleteElement=KB.dom('span').attr('id','file-delete-'+index).html('<a href="#"><i class="fa fa-trash fa-fw"></i></a>').on('click',function(){files.splice(index,1);KB.find('#file-item-'+index).remove();showFiles()}).build();var itemElement=KB.dom('li').attr('id','file-item-'+index).add(deleteElement).add(progressElement).text(' '+files[index].name+' ').add(percentageElement);if(options.maxSize>0&&files[index].size>options.maxSize){itemElement.add(KB.dom('div').addClass('file-error').text(options.labelOversize).build());isOversize=!0}
if(isOversize){KB.trigger('modal.disable')} if(isOversize){KB.trigger('modal.disable')}
return itemElement.build()} return itemElement.build()}
function buildFileListElement(){var fileListElement=KB.dom('ul').attr('id','file-list').build();for(var i=0;i<files.length;i++){fileListElement.appendChild(buildFileListItem(i))} function buildFileListElement(){var fileListElement=KB.dom('ul').attr('id','file-list').build();for(var i=0;i<files.length;i++){fileListElement.appendChild(buildFileListItem(i))}
@ -305,4 +305,4 @@ localStorage.setItem(this.getStorageKey(),JSON.stringify(swimlaneIds));$('.board
$('.board-swimlane-columns-'+swimlaneId+':not(:first-child)').css('display','none');$('.board-swimlane-tasks-'+swimlaneId).css('display','none');$('.hide-icon-swimlane-'+swimlaneId).css('display','none');$('.show-icon-swimlane-'+swimlaneId).css('display','inline')};Kanboard.Swimlane.prototype.isCollapsed=function(swimlaneId){return this.getAllCollapsed().indexOf(swimlaneId)>-1};Kanboard.Swimlane.prototype.getAllCollapsed=function(){return JSON.parse(localStorage.getItem(this.getStorageKey()))||[]};Kanboard.Swimlane.prototype.dragAndDrop=function(){var self=this;$(".draggable-row-handle").mouseenter(function(){$(this).parent().parent().addClass("draggable-item-hover")}).mouseleave(function(){$(this).parent().parent().removeClass("draggable-item-hover")});$(".swimlanes-table tbody").sortable({forcePlaceholderSize:!0,handle:"td:first i",helper:function(e,ui){ui.children().each(function(){$(this).width($(this).width())});return ui},stop:function(event,ui){var swimlane=ui.item;swimlane.removeClass("draggable-item-selected");self.savePosition(swimlane.data("swimlane-id"),swimlane.index()+1)},start:function(event,ui){ui.item.addClass("draggable-item-selected")}}).disableSelection()};Kanboard.Swimlane.prototype.savePosition=function(swimlaneId,position){var url=$(".swimlanes-table").data("save-position-url");var self=this;this.app.showLoadingIcon();$.ajax({cache:!1,url:url,contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({"swimlane_id":swimlaneId,"position":position}),complete:function(){self.app.hideLoadingIcon()}})};Kanboard.Swimlane.prototype.expandSingleSwimlane=function(){if($("tr.board-swimlane").length==1){localStorage.removeItem(this.getStorageKey())}} $('.board-swimlane-columns-'+swimlaneId+':not(:first-child)').css('display','none');$('.board-swimlane-tasks-'+swimlaneId).css('display','none');$('.hide-icon-swimlane-'+swimlaneId).css('display','none');$('.show-icon-swimlane-'+swimlaneId).css('display','inline')};Kanboard.Swimlane.prototype.isCollapsed=function(swimlaneId){return this.getAllCollapsed().indexOf(swimlaneId)>-1};Kanboard.Swimlane.prototype.getAllCollapsed=function(){return JSON.parse(localStorage.getItem(this.getStorageKey()))||[]};Kanboard.Swimlane.prototype.dragAndDrop=function(){var self=this;$(".draggable-row-handle").mouseenter(function(){$(this).parent().parent().addClass("draggable-item-hover")}).mouseleave(function(){$(this).parent().parent().removeClass("draggable-item-hover")});$(".swimlanes-table tbody").sortable({forcePlaceholderSize:!0,handle:"td:first i",helper:function(e,ui){ui.children().each(function(){$(this).width($(this).width())});return ui},stop:function(event,ui){var swimlane=ui.item;swimlane.removeClass("draggable-item-selected");self.savePosition(swimlane.data("swimlane-id"),swimlane.index()+1)},start:function(event,ui){ui.item.addClass("draggable-item-selected")}}).disableSelection()};Kanboard.Swimlane.prototype.savePosition=function(swimlaneId,position){var url=$(".swimlanes-table").data("save-position-url");var self=this;this.app.showLoadingIcon();$.ajax({cache:!1,url:url,contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({"swimlane_id":swimlaneId,"position":position}),complete:function(){self.app.hideLoadingIcon()}})};Kanboard.Swimlane.prototype.expandSingleSwimlane=function(){if($("tr.board-swimlane").length==1){localStorage.removeItem(this.getStorageKey())}}
Kanboard.Task=function(app){this.app=app};Kanboard.Task.prototype.onPopoverOpened=function(){var self=this;self.renderColorPicker();$(document).on("click",".assign-me",function(e){var currentId=$(this).data("current-id");var dropdownId="#modal-box #"+$(this).data("target-id");e.preventDefault();if($(dropdownId+' option[value='+currentId+']').length){$(dropdownId).val(currentId)}})};Kanboard.Task.prototype.renderColorPicker=function(){function renderColorOption(color){return $('<div class="color-picker-option">'+'<div class="color-picker-square color-'+color.id+'"></div>'+'<div class="color-picker-label">'+color.text+'</div>'+'</div>')} Kanboard.Task=function(app){this.app=app};Kanboard.Task.prototype.onPopoverOpened=function(){var self=this;self.renderColorPicker();$(document).on("click",".assign-me",function(e){var currentId=$(this).data("current-id");var dropdownId="#modal-box #"+$(this).data("target-id");e.preventDefault();if($(dropdownId+' option[value='+currentId+']').length){$(dropdownId).val(currentId)}})};Kanboard.Task.prototype.renderColorPicker=function(){function renderColorOption(color){return $('<div class="color-picker-option">'+'<div class="color-picker-square color-'+color.id+'"></div>'+'<div class="color-picker-label">'+color.text+'</div>'+'</div>')}
$(".color-picker").select2({minimumResultsForSearch:Infinity,templateResult:renderColorOption,templateSelection:renderColorOption})};Kanboard.BoardDragAndDrop=function(app){this.app=app;this.savingInProgress=!1};Kanboard.BoardDragAndDrop.prototype.execute=function(){if(this.app.hasId("board")){this.executeListeners();this.dragAndDrop()}};Kanboard.BoardDragAndDrop.prototype.dragAndDrop=function(){var self=this;var dropzone=$(".board-task-list");var params={forcePlaceholderSize:!0,tolerance:"pointer",connectWith:".sortable-column:visible",placeholder:"draggable-placeholder",items:".draggable-item",stop:function(event,ui){var task=ui.item;var taskId=task.attr('data-task-id');var taskPosition=task.attr('data-position');var taskColumnId=task.attr('data-column-id');var taskSwimlaneId=task.attr('data-swimlane-id');var newColumnId=task.parent().attr("data-column-id");var newSwimlaneId=task.parent().attr('data-swimlane-id');var newPosition=task.index()+1;task.removeClass("draggable-item-selected");if(newColumnId!=taskColumnId||newSwimlaneId!=taskSwimlaneId||newPosition!=taskPosition){self.changeTaskState(taskId);self.save(taskId,taskColumnId,newColumnId,newPosition,newSwimlaneId)}},start:function(event,ui){ui.item.addClass("draggable-item-selected");ui.placeholder.height(ui.item.height())}};if(isMobile.any){$(".task-board-sort-handle").css("display","inline");params.handle=".task-board-sort-handle"} $(".color-picker").select2({minimumResultsForSearch:Infinity,templateResult:renderColorOption,templateSelection:renderColorOption})};Kanboard.BoardDragAndDrop=function(app){this.app=app;this.savingInProgress=!1};Kanboard.BoardDragAndDrop.prototype.execute=function(){if(this.app.hasId("board")){this.executeListeners();this.dragAndDrop()}};Kanboard.BoardDragAndDrop.prototype.dragAndDrop=function(){var self=this;var dropzone=$(".board-task-list");var params={forcePlaceholderSize:!0,tolerance:"pointer",connectWith:".sortable-column:visible",placeholder:"draggable-placeholder",items:".draggable-item",stop:function(event,ui){var task=ui.item;var taskId=task.attr('data-task-id');var taskPosition=task.attr('data-position');var taskColumnId=task.attr('data-column-id');var taskSwimlaneId=task.attr('data-swimlane-id');var newColumnId=task.parent().attr("data-column-id");var newSwimlaneId=task.parent().attr('data-swimlane-id');var newPosition=task.index()+1;task.removeClass("draggable-item-selected");if(newColumnId!=taskColumnId||newSwimlaneId!=taskSwimlaneId||newPosition!=taskPosition){self.changeTaskState(taskId);self.save(taskId,taskColumnId,newColumnId,newPosition,newSwimlaneId)}},start:function(event,ui){ui.item.addClass("draggable-item-selected");ui.placeholder.height(ui.item.height())}};if(isMobile.any){$(".task-board-sort-handle").css("display","inline");params.handle=".task-board-sort-handle"}
dropzone.each(function(){$(this).css("min-height",$(this).parent().height())});dropzone.sortable(params)};Kanboard.BoardDragAndDrop.prototype.changeTaskState=function(taskId){var task=$("div[data-task-id="+taskId+"]");task.addClass('task-board-saving-state');task.find('.task-board-saving-icon').show()};Kanboard.BoardDragAndDrop.prototype.save=function(taskId,srcColumnId,dstColumnId,position,swimlaneId){var self=this;self.app.showLoadingIcon();self.savingInProgress=!0;$.ajax({cache:!1,url:$("#board").data("save-url"),contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({"task_id":taskId,"src_column_id":srcColumnId,"dst_column_id":dstColumnId,"swimlane_id":swimlaneId,"position":position}),success:function(data){self.refresh(data);self.savingInProgress=!1},error:function(){self.app.hideLoadingIcon();self.savingInProgress=!1},statusCode:{403:function(data){window.alert(data.responseJSON.message);document.location.reload(!0)}}})};Kanboard.BoardDragAndDrop.prototype.refresh=function(data){$("#board-container").replaceWith(data);this.app.hideLoadingIcon();this.executeListeners();this.dragAndDrop()};Kanboard.BoardDragAndDrop.prototype.executeListeners=function(){for(var className in this.app.controllers){var controller=this.app.get(className);if(typeof controller.onBoardRendered==="function"){controller.onBoardRendered()}}};var _KB=null;jQuery(document).ready(function(){_KB=new Kanboard.App();_KB.execute()}) dropzone.each(function(){$(this).css("min-height",$(this).parent().height())});dropzone.sortable(params)};Kanboard.BoardDragAndDrop.prototype.changeTaskState=function(taskId){var task=$("div[data-task-id="+taskId+"]");task.addClass('task-board-saving-state');task.find('.task-board-saving-icon').show()};Kanboard.BoardDragAndDrop.prototype.save=function(taskId,srcColumnId,dstColumnId,position,swimlaneId){var self=this;self.app.showLoadingIcon();self.savingInProgress=!0;$.ajax({cache:!1,url:$("#board").data("save-url"),contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({"task_id":taskId,"src_column_id":srcColumnId,"dst_column_id":dstColumnId,"swimlane_id":swimlaneId,"position":position}),success:function(data){self.refresh(data);self.savingInProgress=!1},error:function(){self.app.hideLoadingIcon();self.savingInProgress=!1},statusCode:{403:function(data){window.alert(data.responseJSON.message);document.location.reload(!0)}}})};Kanboard.BoardDragAndDrop.prototype.refresh=function(data){$("#board-container").replaceWith(data);this.app.hideLoadingIcon();this.executeListeners();this.dragAndDrop()};Kanboard.BoardDragAndDrop.prototype.executeListeners=function(){for(var className in this.app.controllers){var controller=this.app.get(className);if(typeof controller.onBoardRendered==="function"){controller.onBoardRendered()}}};var _KB=null;jQuery(document).ready(function(){_KB=new Kanboard.App();_KB.execute()})

View File

@ -179,7 +179,7 @@ KB.component('file-upload', function (containerElement, options) {
.text(' ' + files[index].name + ' ') .text(' ' + files[index].name + ' ')
.add(percentageElement); .add(percentageElement);
if (files[index].size > options.maxSize) { if (options.maxSize > 0 && files[index].size > options.maxSize) {
itemElement.add(KB.dom('div').addClass('file-error').text(options.labelOversize).build()); itemElement.add(KB.dom('div').addClass('file-error').text(options.labelOversize).build());
isOversize = true; isOversize = true;
} }

View File

@ -4,6 +4,13 @@ require_once __DIR__.'/Base.php';
class FunctionTest extends Base class FunctionTest extends Base
{ {
public function testConvertPHPSizeToBytes()
{
$this->assertEquals(2097152, convert_php_size_to_bytes('2M'));
$this->assertEquals(2048, convert_php_size_to_bytes('2 k'));
$this->assertEquals(0, convert_php_size_to_bytes('0'));
}
public function testArrayColumnSum() public function testArrayColumnSum()
{ {
$input = array( $input = array(