Merge branch 'MDL-33671-master-3' of git://github.com/peterRd/moodle
This commit is contained in:
+4
-1
@@ -113,13 +113,16 @@ class core_files_renderer extends plugin_renderer_base {
|
||||
array('invalidjson', 'repository'), array('popupblockeddownload', 'repository'),
|
||||
array('unknownoriginal', 'repository'), array('confirmdeletefolder', 'repository'),
|
||||
array('confirmdeletefilewithhref', 'repository'), array('confirmrenamefolder', 'repository'),
|
||||
array('confirmrenamefile', 'repository'), array('newfolder', 'repository'), array('edit', 'moodle')
|
||||
array('confirmrenamefile', 'repository'), array('newfolder', 'repository'), array('edit', 'moodle'),
|
||||
['nofilesselected', 'repository'], ['confirmdeleteselectedfile', 'repository'],
|
||||
['selectall', 'moodle'], ['deselectall', 'moodle'], ['selectallornone', 'form'],
|
||||
)
|
||||
);
|
||||
if ($this->page->requires->should_create_one_time_item_now('core_file_managertemplate')) {
|
||||
$this->page->requires->js_init_call('M.form_filemanager.set_templates',
|
||||
array($this->filemanager_js_templates()), true, $module);
|
||||
}
|
||||
$this->page->requires->js_call_amd('core/checkbox-toggleall', 'init');
|
||||
$this->page->requires->js_init_call('M.form_filemanager.init', array($fm->options), true, $module);
|
||||
|
||||
// non javascript file manager
|
||||
|
||||
@@ -67,6 +67,7 @@ $string['configsyncfiletimeout'] = 'Timeout in seconds for synchronising the ext
|
||||
$string['configsyncimagetimeout'] = 'Timeout in seconds for downloading an image file from external repository during synchronisation.';
|
||||
$string['confirmdelete'] = 'Are you sure you want to delete the repository {$a}? If you choose "Continue and download", file references to external contents will be downloaded to Moodle. This could take a long time to process.';
|
||||
$string['confirmdeletefile'] = 'Are you sure you want to delete this file?';
|
||||
$string['confirmdeleteselectedfile'] = 'Are you sure you want to delete the selected {$a} file(s)?';
|
||||
$string['confirmrenamefile'] = 'Are you sure you want to rename/move this file? There are {$a} alias/shortcut files that use this file as their source. If you proceed then those aliases will be converted to true copies.';
|
||||
$string['confirmdeletefilewithhref'] = 'Are you sure you want to delete this file? There are {$a} alias/shortcut files that use this file as their source. If you proceed then those aliases will be converted to true copies.';
|
||||
$string['confirmdeletefolder'] = 'Are you sure you want to delete this folder? All files and subfolders will be deleted.';
|
||||
@@ -97,6 +98,8 @@ $string['displaytree'] = 'Display folder as file tree';
|
||||
$string['download'] = 'Download';
|
||||
$string['downloadallfiles'] = 'Download all files';
|
||||
$string['downloadfolder'] = 'Download all';
|
||||
$string['downloadselected'] = 'Download selected files';
|
||||
$string['deleteselected'] = 'Delete selected';
|
||||
$string['downloadsucc'] = 'The file has been downloaded successfully';
|
||||
$string['draftareanofiles'] = 'Cannot be downloaded because there is no files attached';
|
||||
$string['editrepositoryinstance'] = 'Edit repository instance';
|
||||
@@ -174,6 +177,7 @@ $string['newfoldername'] = 'New folder name';
|
||||
$string['noenter'] = 'Nothing entered';
|
||||
$string['nofilesattached'] = 'No files attached';
|
||||
$string['nofilesavailable'] = 'No files available';
|
||||
$string['nofilesselected'] = 'No files selected';
|
||||
$string['nomorefiles'] = 'No more attachments allowed';
|
||||
$string['nopathselected'] = 'No destination path select yet (double click tree node to select)';
|
||||
$string['nopermissiontoaccess'] = 'No permission to access this repository.';
|
||||
|
||||
@@ -221,20 +221,23 @@ class behat_field_manager {
|
||||
}
|
||||
|
||||
// If the type is explictly set on the element pointed to by the label - use it.
|
||||
if ($type = $fieldnode->getParent()->getAttribute('data-fieldtype')) {
|
||||
if ($type == 'tags') {
|
||||
$fieldtype = $fieldnode->getAttribute('data-fieldtype');
|
||||
if ($fieldtype) {
|
||||
if ($fieldtype == 'tags') {
|
||||
return 'autocomplete';
|
||||
}
|
||||
return $type;
|
||||
return $fieldtype;
|
||||
}
|
||||
|
||||
if (!empty($fieldnode->find('xpath', '/ancestor::*[@data-passwordunmaskid]'))) {
|
||||
return 'passwordunmask';
|
||||
}
|
||||
|
||||
// We look for a parent node with 'felement' class.
|
||||
if ($class = $fieldnode->getParent()->getAttribute('class')) {
|
||||
// Fetch the parentnode only once.
|
||||
$parentnode = $fieldnode->getParent();
|
||||
|
||||
// We look for a parent node with 'felement' class.
|
||||
if ($class = $parentnode->getAttribute('class')) {
|
||||
if (strstr($class, 'felement') != false) {
|
||||
// Remove 'felement f' from class value.
|
||||
return substr($class, 10);
|
||||
@@ -246,7 +249,7 @@ class behat_field_manager {
|
||||
}
|
||||
}
|
||||
|
||||
return self::get_field_node_type($fieldnode->getParent(), $session);
|
||||
return self::get_field_node_type($parentnode, $session);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+66
-1
@@ -269,10 +269,28 @@ M.form_filemanager.init = function(Y, options) {
|
||||
is_disabled: function() {
|
||||
return this.filemanager.ancestor('.fitem.disabled') != null;
|
||||
},
|
||||
getSelectedFiles: function() {
|
||||
var markedFiles = this.filemanager.all('[data-togglegroup=file-selections]:checked');
|
||||
var filenames = [];
|
||||
markedFiles.each(function(item) {
|
||||
var fileinfo = this.options.list.find(function(element) {
|
||||
return item.getData().fullname == element.fullname;
|
||||
});
|
||||
if (fileinfo && fileinfo != undefined) {
|
||||
filenames.push({
|
||||
filepath: fileinfo.filepath,
|
||||
filename: fileinfo.filename
|
||||
});
|
||||
}
|
||||
}, this);
|
||||
|
||||
return filenames;
|
||||
},
|
||||
setup_buttons: function() {
|
||||
var button_download = this.filemanager.one('.fp-btn-download');
|
||||
var button_create = this.filemanager.one('.fp-btn-mkdir');
|
||||
var button_addfile = this.filemanager.one('.fp-btn-add');
|
||||
var buttonDeleteFile = this.filemanager.one('.fp-btn-delete');
|
||||
|
||||
// setup 'add file' button
|
||||
button_addfile.on('click', this.show_filepicker, this);
|
||||
@@ -378,11 +396,13 @@ M.form_filemanager.init = function(Y, options) {
|
||||
return;
|
||||
}
|
||||
image_downloading.setStyle('display', 'inline');
|
||||
var filenames = this.getSelectedFiles();
|
||||
|
||||
// perform downloaddir ajax request
|
||||
this.request({
|
||||
action: 'downloaddir',
|
||||
action: 'downloadselected',
|
||||
scope: scope,
|
||||
params: {selected: Y.JSON.stringify(filenames)},
|
||||
callback: function(id, obj, args) {
|
||||
var image_downloading = scope.filemanager.one('.fp-img-downloading');
|
||||
image_downloading.setStyle('display', 'none');
|
||||
@@ -403,6 +423,43 @@ M.form_filemanager.init = function(Y, options) {
|
||||
});
|
||||
}, this);
|
||||
|
||||
buttonDeleteFile.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
var dialogOptions = {};
|
||||
var filenames = this.getSelectedFiles();
|
||||
|
||||
if (!filenames.length) {
|
||||
this.print_msg(M.util.get_string('nofilesselected', 'repository'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
dialogOptions.scope = this;
|
||||
var params = {
|
||||
selected: Y.JSON.stringify(filenames)
|
||||
};
|
||||
dialogOptions.header = M.util.get_string('confirm', 'moodle');
|
||||
dialogOptions.message = M.util.get_string('confirmdeleteselectedfile', 'repository', filenames.length);
|
||||
dialogOptions.callbackargs = [params];
|
||||
dialogOptions.callback = function(params) {
|
||||
this.request({
|
||||
action: 'deleteselected',
|
||||
scope: this,
|
||||
params: params,
|
||||
callback: function(id, obj, args) {
|
||||
// Do something here
|
||||
args.scope.filecount -= params.length;
|
||||
if (obj && obj.length) {
|
||||
args.scope.refresh(obj[0]);
|
||||
}
|
||||
if (typeof M.core_formchangechecker != 'undefined') {
|
||||
M.core_formchangechecker.set_form_changed();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
this.show_confirm_dialog(dialogOptions);
|
||||
}, this);
|
||||
|
||||
this.filemanager.all('.fp-vb-icons,.fp-vb-tree,.fp-vb-details').
|
||||
on('click', function(e) {
|
||||
e.preventDefault();
|
||||
@@ -569,10 +626,17 @@ M.form_filemanager.init = function(Y, options) {
|
||||
this.viewmode = 1;
|
||||
element_template = Y.Node.create(M.form_filemanager.templates.iconfilename);
|
||||
}
|
||||
|
||||
if (this.viewmode == 1 || this.viewmode == 2) {
|
||||
this.filemanager.one('.fp-btn-delete').addClass('d-none');
|
||||
} else {
|
||||
this.filemanager.one('.fp-btn-delete').removeClass('d-none');
|
||||
}
|
||||
var options = {
|
||||
viewmode : this.viewmode,
|
||||
appendonly : appendfiles != null,
|
||||
filenode : element_template,
|
||||
disablecheckboxes: false,
|
||||
callbackcontext : this,
|
||||
callback : function(e, node) {
|
||||
if (e.preventDefault) { e.preventDefault(); }
|
||||
@@ -783,6 +847,7 @@ M.form_filemanager.init = function(Y, options) {
|
||||
node.one('.fp-dlg-butcancel').on('click', handle_cancel, this);
|
||||
}
|
||||
this.confirm_dlg.dlgopt = dialog_options;
|
||||
this.confirm_dlg.set('headerContent', dialog_options.header);
|
||||
this.confirm_dlg_node.one('.fp-dlg-text').setContent(dialog_options.message);
|
||||
this.confirm_dlg.show();
|
||||
},
|
||||
|
||||
@@ -45,10 +45,15 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="fp-btn-download">
|
||||
<a role="button" title="{{#str}}downloadfolder, repository{{/str}}" class="btn btn-secondary btn-sm" href="#">
|
||||
<a role="button" title="{{#str}}downloadselected, repository{{/str}}" class="btn btn-secondary btn-sm" href="#">
|
||||
{{#pix}}a/download_all{{/pix}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="fp-btn-delete">
|
||||
<a role="button" title="{{#str}}deleteselected, repository{{/str}}" class="btn btn-secondary btn-sm" href="#">
|
||||
{{#pix}}i/trash{{/pix}}
|
||||
</a>
|
||||
</div>
|
||||
<span class="fp-img-downloading">
|
||||
<span class="sr-only">{{#str}}loadinghelp{{/str}}</span>
|
||||
{{#pix}}i/loading_small{{/pix}}
|
||||
|
||||
@@ -41,7 +41,7 @@ $action = required_param('action', PARAM_ALPHA);
|
||||
$draftid = required_param('itemid', PARAM_INT);
|
||||
$filepath = optional_param('filepath', '/', PARAM_PATH);
|
||||
|
||||
$user_context = context_user::instance($USER->id);
|
||||
$usercontext = context_user::instance($USER->id);
|
||||
|
||||
echo $OUTPUT->header(); // send headers
|
||||
|
||||
@@ -73,7 +73,7 @@ switch ($action) {
|
||||
$newdirname = required_param('newdirname', PARAM_FILE);
|
||||
|
||||
$fs = get_file_storage();
|
||||
$fs->create_directory($user_context->id, 'user', 'draft', $draftid, file_correct_filepath(file_correct_filepath($filepath).$newdirname));
|
||||
$fs->create_directory($usercontext->id, 'user', 'draft', $draftid, file_correct_filepath(file_correct_filepath($filepath).$newdirname));
|
||||
$return = new stdClass();
|
||||
$return->filepath = $filepath;
|
||||
echo json_encode($return);
|
||||
@@ -82,31 +82,28 @@ switch ($action) {
|
||||
case 'delete':
|
||||
$filename = required_param('filename', PARAM_FILE);
|
||||
$filepath = required_param('filepath', PARAM_PATH);
|
||||
$selectedfile = (object)[
|
||||
'filename' => $filename,
|
||||
'filepath' => $filepath
|
||||
];
|
||||
$return = repository_delete_selected_files($usercontext, 'user', 'draft', $draftid, [$selectedfile]);
|
||||
|
||||
$fs = get_file_storage();
|
||||
$filepath = file_correct_filepath($filepath);
|
||||
$return = new stdClass();
|
||||
if ($stored_file = $fs->get_file($user_context->id, 'user', 'draft', $draftid, $filepath, $filename)) {
|
||||
$parent_path = $stored_file->get_parent_directory()->get_filepath();
|
||||
if ($stored_file->is_directory()) {
|
||||
$files = $fs->get_directory_files($user_context->id, 'user', 'draft', $draftid, $filepath, true);
|
||||
foreach ($files as $file) {
|
||||
$file->delete();
|
||||
}
|
||||
$stored_file->delete();
|
||||
$return->filepath = $parent_path;
|
||||
echo json_encode($return);
|
||||
} else {
|
||||
if($result = $stored_file->delete()) {
|
||||
$return->filepath = $parent_path;
|
||||
echo json_encode($return);
|
||||
} else {
|
||||
echo json_encode(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
echo json_encode(false);
|
||||
if ($return) {
|
||||
$response = new stdClass();
|
||||
$response->filepath = array_keys($return)[0];
|
||||
echo json_encode($response);
|
||||
die;
|
||||
}
|
||||
|
||||
echo json_encode(false);
|
||||
die;
|
||||
|
||||
case 'deleteselected':
|
||||
$selected = required_param('selected', PARAM_RAW);
|
||||
$return = [];
|
||||
$selectedfiles = json_decode($selected);
|
||||
$return = repository_delete_selected_files($usercontext, 'user', 'draft', $draftid, $selectedfiles);
|
||||
echo (json_encode($return ? array_keys($return) : false));
|
||||
die;
|
||||
|
||||
case 'setmainfile':
|
||||
@@ -115,9 +112,9 @@ switch ($action) {
|
||||
|
||||
$filepath = file_correct_filepath($filepath);
|
||||
// reset sort order
|
||||
file_reset_sortorder($user_context->id, 'user', 'draft', $draftid);
|
||||
file_reset_sortorder($usercontext->id, 'user', 'draft', $draftid);
|
||||
// set main file
|
||||
$return = file_set_sortorder($user_context->id, 'user', 'draft', $draftid, $filepath, $filename, 1);
|
||||
$return = file_set_sortorder($usercontext->id, 'user', 'draft', $draftid, $filepath, $filename, 1);
|
||||
echo json_encode($return);
|
||||
die;
|
||||
|
||||
@@ -159,7 +156,7 @@ switch ($action) {
|
||||
$zipper = get_file_packer('application/zip');
|
||||
$fs = get_file_storage();
|
||||
|
||||
$file = $fs->get_file($user_context->id, 'user', 'draft', $draftid, $filepath, '.');
|
||||
$file = $fs->get_file($usercontext->id, 'user', 'draft', $draftid, $filepath, '.');
|
||||
|
||||
$parent_path = $file->get_parent_directory()->get_filepath();
|
||||
|
||||
@@ -167,7 +164,7 @@ switch ($action) {
|
||||
$filepath = array_pop($filepath);
|
||||
$zipfile = repository::get_unused_filename($draftid, $parent_path, $filepath . '.zip');
|
||||
|
||||
if ($newfile = $zipper->archive_to_storage(array($filepath => $file), $user_context->id, 'user', 'draft', $draftid, $parent_path, $zipfile, $USER->id)) {
|
||||
if ($newfile = $zipper->archive_to_storage([$filepath => $file], $usercontext->id, 'user', 'draft', $draftid, $parent_path, $zipfile, $USER->id)) {
|
||||
$return = new stdClass();
|
||||
$return->filepath = $parent_path;
|
||||
echo json_encode($return);
|
||||
@@ -175,36 +172,22 @@ switch ($action) {
|
||||
echo json_encode(false);
|
||||
}
|
||||
die;
|
||||
case 'downloadselected':
|
||||
$selected = required_param('selected', PARAM_RAW);
|
||||
$selectedfiles = json_decode($selected);
|
||||
$return = repository_download_selected_files($usercontext, 'user', 'draft', $draftid, $selectedfiles);
|
||||
echo (json_encode($return));
|
||||
die;
|
||||
|
||||
case 'downloaddir':
|
||||
$filepath = required_param('filepath', PARAM_PATH);
|
||||
|
||||
$zipper = get_file_packer('application/zip');
|
||||
$fs = get_file_storage();
|
||||
$area = file_get_draft_area_info($draftid, $filepath);
|
||||
if ($area['filecount'] == 0 && $area['foldercount'] == 0) {
|
||||
echo json_encode(false);
|
||||
die;
|
||||
}
|
||||
|
||||
$stored_file = $fs->get_file($user_context->id, 'user', 'draft', $draftid, $filepath, '.');
|
||||
if ($filepath === '/') {
|
||||
$filename = get_string('files').'.zip';
|
||||
} else {
|
||||
$filename = explode('/', trim($filepath, '/'));
|
||||
$filename = array_pop($filename) . '.zip';
|
||||
}
|
||||
|
||||
// archive compressed file to an unused draft area
|
||||
$newdraftitemid = file_get_unused_draft_itemid();
|
||||
if ($newfile = $zipper->archive_to_storage(array('/' => $stored_file), $user_context->id, 'user', 'draft', $newdraftitemid, '/', $filename, $USER->id)) {
|
||||
$return = new stdClass();
|
||||
$return->fileurl = moodle_url::make_draftfile_url($newdraftitemid, '/', $filename)->out();
|
||||
$return->filepath = $filepath;
|
||||
echo json_encode($return);
|
||||
} else {
|
||||
echo json_encode(false);
|
||||
}
|
||||
$selectedfile = (object)[
|
||||
'filename' => '',
|
||||
'filepath' => $filepath
|
||||
];
|
||||
$return = repository_download_selected_files($usercontext, 'user', 'draft', $draftid, [$selectedfile]);
|
||||
echo json_encode($return);
|
||||
die;
|
||||
|
||||
case 'unzip':
|
||||
@@ -215,15 +198,15 @@ switch ($action) {
|
||||
|
||||
$fs = get_file_storage();
|
||||
|
||||
$file = $fs->get_file($user_context->id, 'user', 'draft', $draftid, $filepath, $filename);
|
||||
$file = $fs->get_file($usercontext->id, 'user', 'draft', $draftid, $filepath, $filename);
|
||||
|
||||
// Find unused name for directory to extract the archive.
|
||||
$temppath = $fs->get_unused_dirname($user_context->id, 'user', 'draft', $draftid, $filepath. pathinfo($filename, PATHINFO_FILENAME). '/');
|
||||
$temppath = $fs->get_unused_dirname($usercontext->id, 'user', 'draft', $draftid, $filepath. pathinfo($filename, PATHINFO_FILENAME). '/');
|
||||
$donotremovedirs = array();
|
||||
$doremovedirs = array($temppath);
|
||||
// Extract archive and move all files from $temppath to $filepath
|
||||
if ($file->extract_to_storage($zipper, $user_context->id, 'user', 'draft', $draftid, $temppath, $USER->id) !== false) {
|
||||
$extractedfiles = $fs->get_directory_files($user_context->id, 'user', 'draft', $draftid, $temppath, true);
|
||||
if ($file->extract_to_storage($zipper, $usercontext->id, 'user', 'draft', $draftid, $temppath, $USER->id) !== false) {
|
||||
$extractedfiles = $fs->get_directory_files($usercontext->id, 'user', 'draft', $draftid, $temppath, true);
|
||||
$xtemppath = preg_quote($temppath, '|');
|
||||
foreach ($extractedfiles as $file) {
|
||||
$realpath = preg_replace('|^'.$xtemppath.'|', $filepath, $file->get_filepath());
|
||||
@@ -231,7 +214,7 @@ switch ($action) {
|
||||
// Set the source to the extracted file to indicate that it came from archive.
|
||||
$file->set_source(serialize((object)array('source' => $filepath)));
|
||||
}
|
||||
if (!$fs->file_exists($user_context->id, 'user', 'draft', $draftid, $realpath, $file->get_filename())) {
|
||||
if (!$fs->file_exists($usercontext->id, 'user', 'draft', $draftid, $realpath, $file->get_filename())) {
|
||||
// File or directory did not exist, just move it.
|
||||
$file->rename($realpath, $file->get_filename());
|
||||
} else if (!$file->is_directory()) {
|
||||
@@ -250,7 +233,7 @@ switch ($action) {
|
||||
}
|
||||
// Remove remaining temporary directories.
|
||||
foreach (array_diff($doremovedirs, $donotremovedirs) as $filepath) {
|
||||
if ($file = $fs->get_file($user_context->id, 'user', 'draft', $draftid, $filepath, '.')) {
|
||||
if ($file = $fs->get_file($usercontext->id, 'user', 'draft', $draftid, $filepath, '.')) {
|
||||
$file->delete();
|
||||
}
|
||||
}
|
||||
@@ -261,7 +244,7 @@ switch ($action) {
|
||||
$filepath = required_param('filepath', PARAM_PATH);
|
||||
|
||||
$fs = get_file_storage();
|
||||
$file = $fs->get_file($user_context->id, 'user', 'draft', $draftid, $filepath, $filename);
|
||||
$file = $fs->get_file($usercontext->id, 'user', 'draft', $draftid, $filepath, $filename);
|
||||
if (!$file) {
|
||||
echo json_encode(false);
|
||||
} else {
|
||||
@@ -275,7 +258,7 @@ switch ($action) {
|
||||
$filepath = required_param('filepath', PARAM_PATH);
|
||||
|
||||
$fs = get_file_storage();
|
||||
$file = $fs->get_file($user_context->id, 'user', 'draft', $draftid, $filepath, $filename);
|
||||
$file = $fs->get_file($usercontext->id, 'user', 'draft', $draftid, $filepath, $filename);
|
||||
if (!$file) {
|
||||
echo json_encode(false);
|
||||
} else {
|
||||
|
||||
@@ -317,6 +317,36 @@ YUI.add('moodle-core_filepicker', function(Y) {
|
||||
// TODO add tooltip with o.data['title'] (o.value) or o.data['thumbnail_title']
|
||||
return el.getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate slave checkboxes based on toggleall's specification
|
||||
* @param {object} o An object reprsenting the record for the current row.
|
||||
* @return {html} The checkbox html
|
||||
*/
|
||||
var formatCheckbox = function(o) {
|
||||
var el = Y.Node.create('<div/>');
|
||||
|
||||
var checkbox = Y.Node.create('<input/>')
|
||||
.setAttribute('type', 'checkbox')
|
||||
.setAttribute('data-fieldtype', 'checkbox')
|
||||
.setAttribute('data-fullname', o.data.fullname)
|
||||
.setAttribute('data-action', 'toggle')
|
||||
.setAttribute('data-toggle', 'slave')
|
||||
.setAttribute('data-togglegroup', 'file-selections')
|
||||
.setAttribute('data-toggle-selectall', M.util.get_string('selectall', 'moodle'))
|
||||
.setAttribute('data-toggle-deselectall', M.util.get_string('deselectall', 'moodle'));
|
||||
|
||||
var checkboxLabel = Y.Node.create('<label>')
|
||||
.setHTML("Select file '" + o.data.fullname + "'")
|
||||
.addClass('sr-only')
|
||||
.setAttrs({
|
||||
for: checkbox.generateID(),
|
||||
});
|
||||
|
||||
el.appendChild(checkbox);
|
||||
el.appendChild(checkboxLabel);
|
||||
return el.getContent();
|
||||
};
|
||||
/** sorting function for table view */
|
||||
var sortFoldersFirst = function(a, b, desc) {
|
||||
if (a.get('isfolder') && !b.get('isfolder')) {
|
||||
@@ -340,6 +370,37 @@ YUI.add('moodle-core_filepicker', function(Y) {
|
||||
{key: "mimetype", label: M.util.get_string('type', 'repository'), allowHTML: true,
|
||||
sortable: true, sortFn: sortFoldersFirst}
|
||||
];
|
||||
|
||||
// Generate a checkbox based on toggleall's specification
|
||||
var div = Y.Node.create('<div/>');
|
||||
var checkbox = Y.Node.create('<input/>')
|
||||
.setAttribute('type', 'checkbox')
|
||||
// .setAttribute('title', M.util.get_string('selectallornone', 'form'))
|
||||
.setAttribute('data-action', 'toggle')
|
||||
.setAttribute('data-toggle', 'master')
|
||||
.setAttribute('data-togglegroup', 'file-selections');
|
||||
|
||||
var checkboxLabel = Y.Node.create('<label>')
|
||||
.setHTML(M.util.get_string('selectallornone', 'form'))
|
||||
.addClass('sr-only')
|
||||
.setAttrs({
|
||||
for: checkbox.generateID(),
|
||||
});
|
||||
|
||||
div.appendChild(checkboxLabel);
|
||||
div.appendChild(checkbox);
|
||||
|
||||
|
||||
// Enable the selectable checkboxes
|
||||
if (options.disablecheckboxes != undefined && !options.disablecheckboxes) {
|
||||
cols.unshift({
|
||||
key: "",
|
||||
label: div.getContent(),
|
||||
allowHTML: true,
|
||||
formatter: formatCheckbox,
|
||||
sortable: false
|
||||
});
|
||||
}
|
||||
scope.tableview = new Y.DataTable({columns: cols, data: fileslist});
|
||||
scope.tableview.delegate('click', function (e, tableview) {
|
||||
var record = tableview.getRecord(e.currentTarget.get('id'));
|
||||
@@ -350,7 +411,8 @@ YUI.add('moodle-core_filepicker', function(Y) {
|
||||
}
|
||||
Y.bind(callback, this)(e, record.getAttrs());
|
||||
}
|
||||
}, 'tr', options.callbackcontext, scope.tableview);
|
||||
}, 'tr td:not(:first-child)', options.callbackcontext, scope.tableview);
|
||||
|
||||
if (options.rightclickcallback) {
|
||||
scope.tableview.delegate('contextmenu', function (e, tableview) {
|
||||
var record = tableview.getRecord(e.currentTarget.get('id'));
|
||||
|
||||
@@ -3209,3 +3209,101 @@ function initialise_filepicker($args) {
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to handle deletion of files.
|
||||
*
|
||||
* @param object $context The context where the delete is called
|
||||
* @param string $component component
|
||||
* @param string $filearea filearea
|
||||
* @param int $itemid the item id
|
||||
* @param array $files Array of files object with each item having filename/filepath as values
|
||||
* @return array $return Array of strings matching up to the parent directory of the deleted files
|
||||
* @throws coding_exception
|
||||
*/
|
||||
function repository_delete_selected_files($context, string $component, string $filearea, $itemid, array $files) {
|
||||
$fs = get_file_storage();
|
||||
$return = [];
|
||||
|
||||
foreach ($files as $selectedfile) {
|
||||
$filename = clean_filename($selectedfile->filename);
|
||||
$filepath = clean_param($selectedfile->filepath, PARAM_PATH);
|
||||
$filepath = file_correct_filepath($filepath);
|
||||
|
||||
if ($storedfile = $fs->get_file($context->id, $component, $filearea, $itemid, $filepath, $filename)) {
|
||||
$parentpath = $storedfile->get_parent_directory()->get_filepath();
|
||||
if ($storedfile->is_directory()) {
|
||||
$files = $fs->get_directory_files($context->id, $component, $filearea, $itemid, $filepath, true);
|
||||
foreach ($files as $file) {
|
||||
$file->delete();
|
||||
}
|
||||
$storedfile->delete();
|
||||
$return[$parentpath] = "";
|
||||
} else {
|
||||
if ($result = $storedfile->delete()) {
|
||||
$return[$parentpath] = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to handle deletion of files.
|
||||
*
|
||||
* @param object $context The context where the delete is called
|
||||
* @param string $component component
|
||||
* @param string $filearea filearea
|
||||
* @param int $itemid the item id
|
||||
* @param array $files Array of files object with each item having filename/filepath as values
|
||||
* @return array $return Array of strings matching up to the parent directory of the deleted files
|
||||
* @throws coding_exception
|
||||
*/
|
||||
function repository_download_selected_files($context, string $component, string $filearea, $itemid, array $files) {
|
||||
global $USER;
|
||||
$return = false;
|
||||
|
||||
$zipper = get_file_packer('application/zip');
|
||||
$fs = get_file_storage();
|
||||
// Archive compressed file to an unused draft area.
|
||||
$newdraftitemid = file_get_unused_draft_itemid();
|
||||
$filestoarchive = [];
|
||||
|
||||
foreach ($files as $selectedfile) {
|
||||
$filename = clean_filename($selectedfile->filename); // Default to '.' for root.
|
||||
$filepath = clean_param($selectedfile->filepath, PARAM_PATH); // Default to '/' for downloadall.
|
||||
$filepath = file_correct_filepath($filepath);
|
||||
$area = file_get_draft_area_info($itemid, $filepath);
|
||||
if ($area['filecount'] == 0 && $area['foldercount'] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$storedfile = $fs->get_file($context->id, $component, $filearea, $itemid, $filepath, $filename);
|
||||
// If it is empty we are downloading a directory.
|
||||
$archivefile = $storedfile->get_filename();
|
||||
if (!$filename || $filename == '.' ) {
|
||||
$archivefile = $filepath;
|
||||
}
|
||||
|
||||
$filestoarchive[$archivefile] = $storedfile;
|
||||
}
|
||||
$zippedfile = get_string('files') . '.zip';
|
||||
if ($newfile =
|
||||
$zipper->archive_to_storage(
|
||||
$filestoarchive,
|
||||
$context->id,
|
||||
$component,
|
||||
$filearea,
|
||||
$newdraftitemid,
|
||||
"/",
|
||||
$zippedfile, $USER->id)
|
||||
) {
|
||||
$return = new stdClass();
|
||||
$return->fileurl = moodle_url::make_draftfile_url($newdraftitemid, '/', $zippedfile)->out();
|
||||
$return->filepath = $filepath;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -19,3 +19,78 @@ Feature: Delete files and folders from the file manager
|
||||
And I delete "Delete me" from "Files" filemanager
|
||||
And I press "Save changes"
|
||||
And I should not see "Delete me"
|
||||
|
||||
@javascript
|
||||
Scenario: Delete a file and a folder using bulk functionality (individually)
|
||||
Given I log in as "admin"
|
||||
And I follow "Manage private files"
|
||||
And I upload "lib/tests/fixtures/empty.txt" file to "Files" filemanager
|
||||
And I create "Delete me later" folder in "Files" filemanager
|
||||
And I press "Save changes"
|
||||
And I follow "Manage private files"
|
||||
And I click on "Display folder with file details" "link"
|
||||
And I set the field "Select file 'empty.txt'" to "1"
|
||||
When I click on "Delete selected" "link"
|
||||
Then I should see "Are you sure you want to delete the selected 1 file(s)?"
|
||||
When I click on "OK" "button" in the "Confirm" "dialogue"
|
||||
Then I should not see "empty.txt"
|
||||
But I should see "Delete me later"
|
||||
When I press "Save changes"
|
||||
And I follow "Manage private files"
|
||||
Then I should not see "empty.txt"
|
||||
But I should see "Delete me later"
|
||||
And I set the field "Select file 'Delete me later'" to "1"
|
||||
And I click on "Delete selected" "link"
|
||||
And I click on "OK" "button" in the "Confirm" "dialogue"
|
||||
Then I should not see "Delete me later"
|
||||
When I press "Save changes"
|
||||
Then I should not see "Delete me later"
|
||||
|
||||
@javascript
|
||||
Scenario: Delete a file and a folder using bulk functionality (multiple)
|
||||
Given I log in as "admin"
|
||||
And I follow "Manage private files"
|
||||
And I upload "lib/tests/fixtures/empty.txt" file to "Files" filemanager
|
||||
And I create "Delete me" folder in "Files" filemanager
|
||||
And I create "Do not delete me" folder in "Files" filemanager
|
||||
And I press "Save changes"
|
||||
And I follow "Manage private files"
|
||||
And I click on "Display folder with file details" "link"
|
||||
And I set the field "Select file 'empty.txt'" to "1"
|
||||
And I set the field "Select file 'Delete me'" to "1"
|
||||
When I click on "Delete selected" "link"
|
||||
Then I should see "Are you sure you want to delete the selected 2 file(s)?"
|
||||
When I click on "OK" "button" in the "Confirm" "dialogue"
|
||||
Then I should not see "Delete me"
|
||||
And I should not see "empty.txt"
|
||||
But I should see "Do not delete me"
|
||||
When I press "Save changes"
|
||||
Then I should not see "Delete me" in the "Private files" "block"
|
||||
And I should not see "empty.txt" in the "Private files" "block"
|
||||
But I should see "Do not delete me" in the "Private files" "block"
|
||||
|
||||
@javascript
|
||||
Scenario: Delete files using the select all checkbox
|
||||
Given I log in as "admin"
|
||||
And I follow "Manage private files"
|
||||
And I upload "lib/tests/fixtures/empty.txt" file to "Files" filemanager
|
||||
And I create "Delete me" folder in "Files" filemanager
|
||||
And I create "Delete me too" folder in "Files" filemanager
|
||||
And I press "Save changes"
|
||||
And I follow "Manage private files"
|
||||
And I click on "Display folder with file details" "link"
|
||||
When I set the field "Select all/none" to "1"
|
||||
Then the following fields match these values:
|
||||
| Select file 'empty.txt' | 1 |
|
||||
| Select file 'Delete me' | 1 |
|
||||
| Select file 'Delete me too' | 1 |
|
||||
When I click on "Delete selected" "link"
|
||||
Then I should see "Are you sure you want to delete the selected 3 file(s)?"
|
||||
When I click on "OK" "button" in the "Confirm" "dialogue"
|
||||
Then I should not see "Delete me"
|
||||
And I should not see "empty.txt"
|
||||
And I should not see "Delete me too"
|
||||
When I press "Save changes"
|
||||
Then I should not see "Delete me" in the "Private files" "block"
|
||||
And I should not see "empty.txt" in the "Private files" "block"
|
||||
But I should not see "Delete me too" in the "Private files" "block"
|
||||
|
||||
@@ -154,6 +154,60 @@ class core_repositorylib_testcase extends advanced_testcase {
|
||||
}
|
||||
}
|
||||
|
||||
public function test_delete_selected_files() {
|
||||
global $USER;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$this->setAdminUser();
|
||||
$fs = get_file_storage();
|
||||
|
||||
$draftitemid = file_get_unused_draft_itemid();
|
||||
$context = context_user::instance($USER->id);
|
||||
|
||||
$dummy = [
|
||||
'contextid' => $context->id,
|
||||
'component' => 'user',
|
||||
'filearea' => 'draft',
|
||||
'itemid' => $draftitemid,
|
||||
'filepath' => '/',
|
||||
'filename' => ''
|
||||
];
|
||||
|
||||
// Create some files.
|
||||
$existingfiles = [
|
||||
'The Matrix.movie',
|
||||
'Astalavista.txt',
|
||||
'foobar',
|
||||
];
|
||||
|
||||
$selectedfiles = [
|
||||
'The Matrix.movie' => [],
|
||||
'Astalavista.txt' => []
|
||||
];
|
||||
foreach ($existingfiles as $filename) {
|
||||
$dummy['filename'] = $filename;
|
||||
$file = $fs->create_file_from_string($dummy, 'Content of ' . $filename);
|
||||
if (array_key_exists($filename, $selectedfiles)) {
|
||||
$selectedfiles[$filename] = (object)[
|
||||
'filename' => $filename,
|
||||
'filepath' => $file->get_filepath()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Get area files with default options.
|
||||
$areafiles = $fs->get_area_files($context->id, 'user', 'draft', $draftitemid);
|
||||
// Should be the 3 files we added plus the folder.
|
||||
$this->assertEquals(4, count($areafiles));
|
||||
|
||||
repository_delete_selected_files($context, 'user', 'draft', $draftitemid, $selectedfiles);
|
||||
|
||||
$areafiles = $fs->get_area_files($context->id, 'user', 'draft', $draftitemid);
|
||||
// Should be the 1 file left plus the folder.
|
||||
$this->assertEquals(2, count($areafiles));
|
||||
}
|
||||
|
||||
public function test_can_be_edited_by_user() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
|
||||
@@ -553,6 +553,7 @@ a.ygtvspacer:hover {
|
||||
.filemanager.fm-maxfiles .fp-btn-add,
|
||||
.filemanager.fm-maxfiles .dndupload-message,
|
||||
.filemanager.fm-noitems .fp-btn-download,
|
||||
.filemanager.fm-noitems .fp-btn-delete,
|
||||
.filemanager .fm-empty-container,
|
||||
.filemanager.fm-noitems .filemanager-container .fp-content {
|
||||
display: none;
|
||||
|
||||
@@ -13933,6 +13933,7 @@ a.ygtvspacer:hover {
|
||||
.filemanager.fm-maxfiles .fp-btn-add,
|
||||
.filemanager.fm-maxfiles .dndupload-message,
|
||||
.filemanager.fm-noitems .fp-btn-download,
|
||||
.filemanager.fm-noitems .fp-btn-delete,
|
||||
.filemanager .fm-empty-container,
|
||||
.filemanager.fm-noitems .filemanager-container .fp-content {
|
||||
display: none; }
|
||||
|
||||
@@ -14148,6 +14148,7 @@ a.ygtvspacer:hover {
|
||||
.filemanager.fm-maxfiles .fp-btn-add,
|
||||
.filemanager.fm-maxfiles .dndupload-message,
|
||||
.filemanager.fm-noitems .fp-btn-download,
|
||||
.filemanager.fm-noitems .fp-btn-delete,
|
||||
.filemanager .fm-empty-container,
|
||||
.filemanager.fm-noitems .filemanager-container .fp-content {
|
||||
display: none; }
|
||||
|
||||
Reference in New Issue
Block a user