Commit 50ec2fee authored by jonnybradley's avatar jonnybradley

[bp/r56398][NEW] upload: New jQuery File Upload interface for file galleries (as a smarty plugin)

Combined backport of these commits: r56398,r56402,r56403,r56406,r56407,r56413,r56419,r56420,r56432,r56433,r56434,r56940 and r57048
Also a composer update. No issues or porblems detected so far but please let me know if you find anything wrong (thanks again marclaporte)
parent bdfb4eef
......@@ -3275,6 +3275,7 @@ lib/jquery_tiki/tiki-ajax_services.js -text
lib/jquery_tiki/tiki-bootstrapmodalfix.js -text
lib/jquery_tiki/tiki-connect.js -text
lib/jquery_tiki/tiki-jquery.js -text
lib/jquery_tiki/tiki-jquery_upload.js -text
lib/jquery_tiki/tiki-maps.js -text
lib/jquery_tiki/tiki-themegenerator.js -text
lib/jquery_tiki/tiki-toolbars.js -text
......@@ -3904,6 +3905,7 @@ lib/smarty_tiki/function.favorite.php -text
lib/smarty_tiki/function.fgal_browse.php -text
lib/smarty_tiki/function.file_selector.php -text
lib/smarty_tiki/function.filegal_manager_url.php -text
lib/smarty_tiki/function.filegal_uploader.php -text
lib/smarty_tiki/function.fileinfo.php -text
lib/smarty_tiki/function.gallery.php -text
lib/smarty_tiki/function.help.php -text
......@@ -5258,6 +5260,7 @@ templates/fgal_context_menu.tpl -text
templates/fgal_listing_conf.tpl -text
templates/file/browse.tpl -text
templates/file/index.php -text
templates/file/jquery_upload.tpl -text
templates/file/list_gallery.tpl -text
templates/file/remote.tpl -text
templates/file/thumbnail_gallery.tpl -text
......
This diff is collapsed.
......@@ -42,6 +42,7 @@ class Services_File_Controller
$asuser = $input->user->text();
if (isset($_FILES['data'])) {
// used by $this->action_upload_multiple and file gallery Files fields (possibly others)
if (is_uploaded_file($_FILES['data']['tmp_name'])) {
$file = new JitFilter($_FILES['data']);
$name = $file->name->text();
......@@ -84,6 +85,96 @@ class Services_File_Controller
);
}
/**
* Uploads several files at once, currently from jquery_upload when file_galleries_use_jquery_upload pref is enabled
*
* @param $input
* @return array
* @throws Services_Exception
* @throws Services_Exception_NotAvailable
*/
function action_upload_multiple($input)
{
global $user;
$filegallib = TikiLib::lib('filegal');
$errorreportlib = TikiLib::lib('errorreport');
$output = ['files' => []];
if (isset($_FILES['files']) && is_array($_FILES['files']['tmp_name'])) {
// a few other params that are still arrays but shouldn't be
$input->offsetSet('galleryId', $input->galleryId->asArray()[0]);
$input->offsetSet('hit_limit', $input->hit_limit->asArray()[0]);
$input->offsetSet('isbatch', $input->isbatch->asArray()[0]);
$input->offsetSet('deleteAfter', $input->deleteAfter->asArray()[0]);
$input->offsetSet('author', $input->author->asArray()[0]);
$input->offsetSet('user', $input->user->asArray()[0]);
$input->offsetSet('listtoalert', $input->listtoalert->asArray()[0]);
for ($i = 0; $i < count($_FILES['files']['tmp_name']); $i++) {
if (is_uploaded_file($_FILES['files']['tmp_name'][$i])) {
$_FILES['data']['name'] = $_FILES['files']['name'][$i];
$_FILES['data']['size'] = $_FILES['files']['size'][$i];
$_FILES['data']['type'] = $_FILES['files']['type'][$i];
$_FILES['data']['tmp_name'] = $_FILES['files']['tmp_name'][$i];
// do the actual upload
$file = $this->action_upload($input);
if (!empty($file['fileId'])) {
$file['info'] = $filegallib->get_file_info($file['fileId']);
// when stored in the database the file contents is here and should not be sent back to the client
$file['info']['data'] = null;
$file['syntax'] = $filegallib->getWikiSyntax($file['galleryId'], $file['info']);
}
if ($input->isbatch->word() && stripos($input->type->text(), 'zip') !== false) {
$errors = [];
$perms = Perms::get(['type' => 'file', 'object' => $file['fileId']]);
if ($perms->batch_upload_files) {
try {
$filegallib->process_batch_file_upload(
$file['galleryId'],
$_FILES['files']['tmp_name'][$i],
$user,
'',
$errors
);
} catch (Exception $e) {
$errorreportlib->report($e->getMessage());
}
if ($errors) {
foreach ($errors as $error) {
$errorreportlib->report($error);
}
} else {
$file['syntax'] = tr('Batch file processed: "%0"', $file['name']); // cheeky?
}
} else {
$errorreportlib->report(tra('No permission to upload zipped file packages'));
}
}
$output['files'][] = $file;
} else {
throw new Services_Exception_NotAvailable(tr('File could not be uploaded.'));
}
}
if ($input->autoupload->word()) {
TikiLib::lib('user')->set_user_preference($user, 'filegals_autoupload', 'y');
} else {
TikiLib::lib('user')->set_user_preference($user, 'filegals_autoupload', 'n');
}
} else {
throw new Services_Exception_NotAvailable(tr('File could not be uploaded.'));
}
return $output;
}
function action_browse($input)
{
try {
......
/*
* jQuery File Upload connector for Tiki
*
* https://github.com/blueimp/jQuery-File-Upload
*
* $Id$
*/
$(function () {
var url = $.service("file", "upload_multiple"), // upload handler:
uploadButton = $('<button/>')
.addClass('btn btn-primary upload')
.prop('disabled', true)
.text(tr('Processing...'))
.on('click', function () {
var $this = $(this),
data = $this.data();
$this
.off('click')
.text('Abort')
.on('click', function () {
$this.parents(".buttons").append(cancelButton.clone(true).data(data).text(tr("Clear")));
$this.remove();
data.abort();
})
.next("button").hide();
data.submit().always(function () {
$this.next("button").show().text(tr("Clear"));
$this.remove();
});
}),
cancelButton = $('<button/>')
.addClass('btn btn-default')
.text(tr('Cancel'))
.on('click', function () {
$(this).parents("div.file-list").remove();
});
$('#fileupload').fileupload({
url: url,
dataType: 'json',
autoUpload: false,
//maxFileSize: 999000,
// Enable image resizing, except for Android and Opera,
// which actually support image resizing, but fail to
// send Blob objects via XHR requests:
disableImageResize: /Android(?!.*Chrome)|Opera/
.test(window.navigator.userAgent),
previewMaxWidth: 100,
previewMaxHeight: 100,
previewCrop: true,
singleFileUploads: false,
filesContainer: $('div.files'),
uploadTemplateId: null,
downloadTemplateId: null,
uploadTemplate: null,
downloadTemplate: null
}).on('fileuploadadd', function (e, data) {
data.context = $('<div/>').addClass("file-list").appendTo('#files');
$.each(data.files, function (index, file) {
var node = $('<p/>').addClass("file-to-upload")
.append("<br>")
.append($('<span/>').text(file.name));
node.appendTo(data.context);
if (index === data.files.length - 1) {
var $div = $("<div/>")
.addClass("buttons")
.append(uploadButton.clone(true).data(data))
.append(cancelButton.clone(true).data(data));
data.context.append($div);
}
});
}).on('fileuploadprocessalways', function (e, data) {
var index = data.index,
file = data.files[index],
context = data.context;
if (!context.length) { // context seems to fail here
context = $("div:contains("+file.name+")", "#files");
}
var node = $(context.children("p")[index]);
if (file.preview) {
if (! $(file.preview).is("canvas")) {
node.css("width", "auto");
}
node.prepend(file.preview);
} else {
var type = "file";
if (file.type.match(/pdf/)) {
type = "pdf";
} else if (file.type.match(/video/)) {
type = "video";
} else if (file.type.match(/audio/)) {
type = "audio";
} else if (file.type.match(/zip/)) {
type = "zip";
}
node.prepend($("#" + type + "_icon").clone().removeAttr("id"));
}
if (file.error) {
node.append($('<span class="text-danger"/>').text(file.error));
}
if (index === data.files.length - 1) {
context.find('button:first')
.text('Upload')
.prop('disabled', !!data.files.error);
var $progdiv = $('<div class="progress margin-bottom-xs"/>')
.append('<div class="progress-bar progress-bar-success progress-bar-striped active" role="progressbar"/>');
context.find(".buttons").prepend($progdiv);
if ($("input[name=autoupload]:checked").length) {
context.find('button:first').click();
}
}
}).on('fileuploaddone', function (e, data) {
$.each(data.result.files, function (index, file) {
var context = data.context;
if (file.fileId) {
var display = file.type.match(/^image\//) ? "display" : "dl",
link = $('<a>')
.attr('target', '_blank');
if (file.type.match(/^image\//)) {
link.prop('href', "tiki-download_file.php?display&fileId=" + file.fileId); // view an image
} else {
link.prop('href', "tiki-list_file_gallery.php?galleryId=" + file.galleryId); // show gallery for other files
}
$(context.children("p")[index])
.children()
.wrap(link)
.find("span").text(file.info.name);
var match = location.search.match(/filegals_manager=([^&]+)/);
if (match) {
$(context).find("a").click(function () {
window.opener.insertAt(match[1], file.syntax);
checkClose();
return false;
}).attr("title", tr("Click here to use the file"));
} else {
$(context).find("a:last")
.after("<br><code>" + file.syntax + "</code>");
if (jqueryTiki.colorbox && file.type.match(/^image\//)) {
context.find("a").colorbox({photo: true});
}
}
} else if (file.error) {
var error = $('<span class="text-danger"/>').text(file.error);
$(context.children()[index])
.append('<br>')
.append(error);
}
if (index === data.files.length - 1) {
context.find(".progress").delay(1000).fadeOut("fast");
}
});
e.preventDefault();
}).on('fileuploadfail', function (e, data) {
$.each(data.files, function (index) {
var error = $('<span class="text-danger"/>').text(tr('File upload failed: ') + data.errorThrown),
context = data.context;
$(context.children()[index])
.append('<br>')
.append(error);
if (index === data.files.length - 1) {
context.find(".progress").delay(1000).fadeOut("fast");
}
});
return false;
}).parents("form").off("submit").on("submit", function (e) {
// submitting the form seems to happen automatically resulting in a white page - not sure how still but this stops it...
return false;
}).prop('disabled', !$.support.fileInput)
.parent().addClass($.support.fileInput ? undefined : 'disabled');
});
......@@ -26,5 +26,16 @@ function prefs_file_list()
),
'default' => 'points_desc',
),
'file_galleries_use_jquery_upload' => array(
'name' => tra('Use jQuery Upload'),
'description' => tra('Use the improved Tiki 14.2+ upload page'),
'type' => 'flag',
'default' => 'n',
'tags' => array('experimental'),
'dependencies' => array(
'feature_file_galleries',
'feature_jquery_ui',
),
),
);
}
<?php
// (c) Copyright 2002-2015 by authors of the Tiki Wiki CMS Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id$
//this script may only be included - so its better to die if called directly.
if (strpos($_SERVER["SCRIPT_NAME"], basename(__FILE__)) !== false) {
header("location: index.php");
exit;
}
/** filegal_uploader: Adds a widget to the page to upload files
*
* @param array $params
* 'galleryId' => int file gallery to upload into by default
*
* @param Smarty $smarty
* @return string html
*/
function smarty_function_filegal_uploader($params, $smarty)
{
$headerlib = TikiLib::lib('header');
// Image loader and canvas libs
$headerlib->add_jsfile('vendor/blueimp/javascript-load-image/js/load-image.all.min.js');
$headerlib->add_jsfile('vendor/blueimp/javascript-canvas-to-blob/js/canvas-to-blob.min.js');
// The Iframe Transport is required for browsers without support for XHR file uploads
$headerlib->add_jsfile('vendor/blueimp/jquery-file-upload/js/jquery.iframe-transport.js');
// The basic File Upload plugin
$headerlib->add_jsfile('vendor/blueimp/jquery-file-upload/js/jquery.fileupload.js');
// The File Upload processing plugin
$headerlib->add_jsfile('vendor/blueimp/jquery-file-upload/js/jquery.fileupload-process.js');
// The File Upload image preview & resize plugin
$headerlib->add_jsfile('vendor/blueimp/jquery-file-upload/js/jquery.fileupload-image.js');
// The File Upload audio preview plugin
$headerlib->add_jsfile('vendor/blueimp/jquery-file-upload/js/jquery.fileupload-audio.js');
// The File Upload video preview plugin
$headerlib->add_jsfile('vendor/blueimp/jquery-file-upload/js/jquery.fileupload-video.js');
// The File Upload validation plugin
$headerlib->add_jsfile('vendor/blueimp/jquery-file-upload/js/jquery.fileupload-validate.js');
// The File Upload user interface plugin
$headerlib->add_jsfile('vendor/blueimp/jquery-file-upload/js/jquery.fileupload-ui.js');
// CSS
$headerlib->add_cssfile('vendor/blueimp/jquery-file-upload/css/jquery.fileupload.css');
$headerlib->add_cssfile('vendor/blueimp/jquery-file-upload/css/jquery.fileupload-ui.css');
// Tiki customised application script
$headerlib->add_jsfile('lib/jquery_tiki/tiki-jquery_upload.js');
$return = $smarty->fetch('file/jquery_upload.tpl');
return $return;
}
......@@ -50,13 +50,14 @@ function smarty_function_html_select_duration($params, $smarty)
} else {
$selected = 604800;
}
$html_result .= '<input name="'.$params['prefix'].'" type="text" size="5" value="'.$params['default'].'" />';
$html_result .= '<div class="col-sm-1">';
$html_result .= '<input name="'.$params['prefix'].'" type="text" size="5" value="'.$params['default'].'" class="form-control"></div>';
if (strstr($params['prefix'], '[]')) {
$prefix = str_replace('[]', '_unit[]', $params['prefix']);
} else {
$prefix = $params['prefix'].'_unit';
}
$html_result .= '<select name="'.$prefix.'">';
$html_result .= '<div class="col-sm-2"><select name="'.$prefix.'" class="form-control">';
$html_result .= smarty_function_html_options(
array(
......@@ -67,7 +68,7 @@ function smarty_function_html_select_duration($params, $smarty)
$smarty
);
$html_result .= '</select>';
$html_result .= '</select></div>';
return $html_result;
}
......
......@@ -75,6 +75,8 @@
{preference name='fgal_allow_duplicates'}
{preference name='feature_file_galleries_batch'}
{preference name='file_galleries_use_jquery_upload'}
<div class="adminoptionboxchild" id="feature_file_galleries_batch_childcontainer">
{remarksbox title="Note"}
{tr}You are highly recommended to use a file directory as the File Gallery storage, when using this feature{/tr}
......
{* $Id$ *}
{* Used by smarty_function_filegal_uploader() when $prefs.file_galleries_use_jquery_upload is enabled *}
{* The fileinput-button span is used to style the file input field as button *}
<div class="form-group">
<div class="col-sm-9 col-sm-offset-3">
<div class="btn btn-success fileinput-button">
{icon name='plus'}
<span>{tr}Add files...{/tr}</span>
{* The file input field used as target for the file upload widget *}
<input id="fileupload" type="file" name="files[]" multiple>
</div>
</div>
</div>
<div class="form-group">
<label for="autoupload" class="col-sm-9 col-sm-offset-3">{* auto-upload user pref *}
<input type="checkbox" id="autoupload" name="autoupload"{if $prefs.filegals_autoupload eq 'y'} checked="checked"{/if}>
{tr}Automatic upload{/tr}
</label>{* The container for the uploaded files *}
</div>
<div class="form-group">
<div class="col-sm-9 col-sm-offset-3">
<div id="files" class="files"></div>
</div>
</div>
<div class="hidden">
{icon name='file' id='file_icon'}
{icon name='pdf' id='pdf_icon'}
{icon name='video' id='video_icon'}
{icon name='audio' id='audio_icon'}
{icon name='zip' id='zip_icon'}
</div>
......@@ -114,43 +114,48 @@
<div class="fgal_file">
<div class="fgal_file_c1">
{if $simpleMode neq 'y'}
<div class="form-group">
<label for="name" class="col-sm-3 control-label">{tr}File title{/tr}</label>
<div class="col-sm-9">
<input class="form-control" type="text" id="name" name="name[]"
{if isset($fileInfo) and $fileInfo.name}
value="{$fileInfo.name|escape}"
{if $prefs.file_galleries_use_jquery_upload neq 'y' or $editFileId}
{if $simpleMode neq 'y'}
<div class="form-group">
<label for="name" class="col-sm-3 control-label">{tr}File title{/tr}</label>
<div class="col-sm-9">
<input class="form-control" type="text" id="name" name="name[]"
{if isset($fileInfo) and $fileInfo.name}
value="{$fileInfo.name|escape}"
{/if}
size="40"
>
{if isset($gal_info.type) and ($gal_info.type eq "podcast" or $gal_info.type eq "vidcast")}
({tr}required field for podcasts{/tr})
{/if}
size="40"
>
{if isset($gal_info.type) and ($gal_info.type eq "podcast" or $gal_info.type eq "vidcast")}
({tr}required field for podcasts{/tr})
{/if}
</div>
</div>
</div>
<div class="form-group">
<label for="description" class="col-sm-3 control-label">{tr}File description{/tr}</label>
<div class="col-sm-9">
<textarea class="form-control" rows="2" cols="40" id="description" name="description[]">{if isset($fileInfo.description)}{$fileInfo.description|escape}{/if}</textarea>
{if isset($gal_info.type) and ($gal_info.type eq "podcast" or $gal_info.type eq "vidcast")}
<br><em>{tr}Required for podcasts{/tr}.</em>
{/if}
<div class="form-group">
<label for="description" class="col-sm-3 control-label">{tr}File description{/tr}</label>
<div class="col-sm-9">
<textarea class="form-control" rows="2" cols="40" id="description" name="description[]">{if isset($fileInfo.description)}{$fileInfo.description|escape}{/if}</textarea>
{if isset($gal_info.type) and ($gal_info.type eq "podcast" or $gal_info.type eq "vidcast")}
<br><em>{tr}Required for podcasts{/tr}.</em>
{/if}
</div>
</div>
</div>
{/if}
{if $prefs.javascript_enabled neq 'y' || !$editFileId}
<div class="form-group">
<label for="userfile" class="col-sm-3 control-label">{tr}Upload from disk{/tr}</label>
<div class="col-sm-9">
{if $editFileId}
{$fileInfo.filename|escape}
{/if}
{/if}
{if $prefs.javascript_enabled neq 'y' || !$editFileId}
<div class="form-group">
<label for="userfile" class="col-sm-3 control-label">{tr}Upload from disk{/tr}</label>
<div class="col-sm-9">
{if $editFileId}
{$fileInfo.filename|escape}
{/if}
<input id="userfile" name="userfile[]" type="file" size="40">
<input id="userfile" name="userfile[]" type="file" size="40">
</div>
</div>
</div>
{/if}
{else}{* file_galleries_use_jquery_upload = y *}
{filegal_uploader}
{/if}
</div>
{if $simpleMode neq 'y'}
......@@ -167,17 +172,11 @@
{if $prefs.fgal_delete_after eq 'y'}
<div class="form-group">
<label for="deleteAfter" class="col-sm-3 control-label">{tr}File can be deleted after{/tr}</label>
<div class="col-sm-9">
{if $editFileId}
{html_select_duration prefix='deleteAfter' default_value=$fileInfo.deleteAfter}
{else}
{if $prefs.feature_jscalendar eq 'y'}
<input type="text" value="" name="deleteAfter[]" class="datePicker">
{else}
{html_select_duration prefix='deleteAfter[]' default_unit=week}
{/if}
{html_select_duration prefix='deleteAfter[]' default_unit=week}
{/if}
</div>
</div>
{/if}
......@@ -190,7 +189,7 @@
<input type="hidden" name="galleryId" value="{$galleryId}">
{elseif empty($groupforalert)}
<div class="form-group">
<label for="galleryId" class="col-sm-3">{tr}File gallery{/tr}</label>
<label for="galleryId" class="col-sm-3 control-label">{tr}File gallery{/tr}</label>
<div class="col-sm-9">
<select id="galleryId" name="galleryId[]" class="form-control">
<option value="{$treeRootId}" {if $treeRootId eq $galleryId}selected="selected"{/if} style="font-style:italic; border-bottom:1px dashed #666;">{tr}Root{/tr}</option>
......@@ -309,25 +308,27 @@
{$upload_str}
{if $editFileId}
{include file='categorize.tpl'}<br>
<div id="page_bar" class="form-group">
<div class="col-sm-9 col-sm-offset-3">
<input name="upload" type="submit" class="btn btn-default" value="{tr}Save{/tr}">
{if $prefs.file_galleries_use_jquery_upload neq 'y'}
{if $editFileId}
{include file='categorize.tpl'}<br>
<div id="page_bar" class="form-group">
<div class="col-sm-9 col-sm-offset-3">
<input name="upload" type="submit" class="btn btn-default" value="{tr}Save{/tr}">
</div>
</div>
</div>
{elseif $prefs.javascript_enabled neq 'y'}
{$upload_str}
{$upload_str}
{include file='categorize.tpl'}<br>
<div id="page_bar" class="form-group">
<div class="col-sm-9 col-sm-offset-3">
<input type="submit" class="btn btn-default btn-sm" name="upload" value="{tr}Upload{/tr}">
{elseif $prefs.javascript_enabled neq 'y'}
{$upload_str}
{$upload_str}
{include file='categorize.tpl'}<br>
<div id="page_bar" class="form-group">
<div class="col-sm-9 col-sm-offset-3">
<input type="submit" class="btn btn-default btn-sm" name="upload" value="{tr}Upload{/tr}">
</div>
</div>
</div>
{/if}
{/if}
{if !$editFileId}
{if !$editFileId && $prefs.file_galleries_use_jquery_upload neq 'y'}
<div id="page_bar" class="form-group">
<div class="col-sm-9 col-sm-offset-3">
<input type="submit" class="btn btn-primary btn-sm"
......@@ -366,7 +367,7 @@
{include file='metadata/meta_view_tabs.tpl'}
{/if}
{if ! $editFileId}
{if ! $editFileId and $prefs.file_galleries_use_jquery_upload neq 'y'}
{if $prefs.feature_jquery_ui eq 'y'}
{jq}$('.datePicker').datepicker({minDate: 0, maxDate: '+1m', dateFormat: 'dd/mm/yy'});{/jq}
{/if}
......
......@@ -1834,6 +1834,38 @@ table.treetable tr.expanded span.indenter a {
#fgalexplorer > div > ul ul {
padding-left: 16px;
}
/* additions for jquery file upload */
.files .file-list {
overflow-x: scroll;
overflow-y: hidden;
white-space: nowrap;
}
.files .file-list .file-to-upload {
display: inline-block;
width: 120px;
overflow: hidden;
padding: 10px;
text-overflow: ellipsis;
}
.files .file-list .file-to-upload span {
font-size: x-small;
}
.files .file-list .file-to-upload span.text-danger {
white-space: normal;
}
.files .file-list .file-to-upload code {
font-size: x-small;
white-space: normal;
}
.files .file-list .buttons {
clear: both;
margin: 10px;
}
.files .file-list span.icon {
font-size: 90px;
width: 100px;
height: 100px;
}
.statuson,
.statusoff {
padding: 3px 2px 0 2px;
......
......@@ -192,3 +192,44 @@ table.treetable tr.expanded span.indenter a {
#fgalexplorer > div > ul ul {
padding-left: 16px;
}
/* additions for jquery file upload */
.files .file-list {
overflow-x: scroll;
overflow-y: hidden;
white-space: nowrap;
}
.files .file-list .file-to-upload {
display: inline-block;
width: 120px;
overflow: hidden;
padding: 10px;
text-overflow: ellipsis;
}