diff --git a/files/renderer.php b/files/renderer.php index 6e168e16e6a7a..9bda16b004f4c 100644 --- a/files/renderer.php +++ b/files/renderer.php @@ -121,6 +121,7 @@ public function render_form_filemanager($fm) { $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 diff --git a/lang/en/repository.php b/lang/en/repository.php index f636ee7a74282..3485ec61ddc33 100644 --- a/lang/en/repository.php +++ b/lang/en/repository.php @@ -98,6 +98,7 @@ $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'; diff --git a/lib/form/filemanager.js b/lib/form/filemanager.js index b3eed11465350..80e2102697baf 100644 --- a/lib/form/filemanager.js +++ b/lib/form/filemanager.js @@ -269,6 +269,23 @@ M.form_filemanager.init = function(Y, options) { is_disabled: function() { return this.filemanager.ancestor('.fitem.disabled') != null; }, + getSelectedFiles: function() { + var markedFiles = this.filemanager.all('.mark-for-selection: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'); @@ -379,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'); @@ -407,19 +426,7 @@ M.form_filemanager.init = function(Y, options) { buttonDeleteFile.on('click', function(e) { e.preventDefault(); var dialogOptions = {}; - var markedForDeletion = this.filemanager.all('.mark-for-deletion:checked'); - var filenames = []; - markedForDeletion.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); + var filenames = this.getSelectedFiles(); if (!filenames.length) { this.print_msg(M.util.get_string('nofilesselected', 'repository'), 'error'); @@ -628,7 +635,7 @@ M.form_filemanager.init = function(Y, options) { viewmode : this.viewmode, appendonly : appendfiles != null, filenode : element_template, - disablecheckboxes : false, + disablecheckboxes: false, callbackcontext : this, callback : function(e, node) { if (e.preventDefault) { e.preventDefault(); } diff --git a/lib/templates/filemanager_page_generallayout.mustache b/lib/templates/filemanager_page_generallayout.mustache index 22d9ffcfb8180..7300170a584d6 100644 --- a/lib/templates/filemanager_page_generallayout.mustache +++ b/lib/templates/filemanager_page_generallayout.mustache @@ -45,7 +45,7 @@
- + {{#pix}}a/download_all{{/pix}}
diff --git a/repository/draftfiles_ajax.php b/repository/draftfiles_ajax.php index eb8c77de023b2..328f2d230d61f 100644 --- a/repository/draftfiles_ajax.php +++ b/repository/draftfiles_ajax.php @@ -172,36 +172,22 @@ 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($usercontext->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(['/' => $stored_file], $usercontext->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': diff --git a/repository/filepicker.js b/repository/filepicker.js index 57616f18de0bb..c191a4cbe99bb 100644 --- a/repository/filepicker.js +++ b/repository/filepicker.js @@ -317,12 +317,23 @@ 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('
'); var checkbox = Y.Node.create(''); checkbox.setAttribute('type', 'checkbox') - .setAttribute('class', 'mark-for-deletion') - .setAttribute('data-fullname', o.data.fullname); + .setAttribute('class', 'mark-for-selection') + .setAttribute('data-fullname', o.data.fullname) + .setAttribute('data-action', 'toggle') + .setAttribute('data-toggle', 'slave') + .setAttribute('data-togglegroup', 'file-selections') + .setAttribute('data-toggle-selectall', 'Select all') + .setAttribute('data-toggle-deselectall', 'Deselectall'); el.appendChild(checkbox); return el.getContent(); @@ -351,10 +362,20 @@ YUI.add('moodle-core_filepicker', function(Y) { sortable: true, sortFn: sortFoldersFirst} ]; + // Generate a checkbox based on toggleall's specification + var checkbox = Y.Node.create(''); + var div = Y.Node.create('
'); + checkbox.setAttribute('type', 'checkbox') + .setAttribute('class', 'mark-for-selection') + .setAttribute('data-action', 'toggle') + .setAttribute('data-toggle', 'master') + .setAttribute('data-togglegroup', 'file-selections'); + div.appendChild(checkbox); + // Enable the selectable checkboxes if (options.disablecheckboxes != undefined && !options.disablecheckboxes) { cols.unshift({ - key: "", label: "", + key: "", label: div.getContent(), allowHTML: true, formatter: formatCheckbox, sortable: false }); @@ -370,14 +391,7 @@ YUI.add('moodle-core_filepicker', function(Y) { Y.bind(callback, this)(e, record.getAttrs()); } }, 'tr td:not(:first-child)', options.callbackcontext, scope.tableview); - scope.tableview.delegate('change', function(e) { - e.preventDefault(); - if (e.target.get('checked')) { - e.container.all('.mark-for-deletion').setAttribute('checked', true); - } else { - e.container.all('.mark-for-deletion').removeAttribute('checked'); - } - }, '#select-all', options.callbackcontext, scope.tableview); + if (options.rightclickcallback) { scope.tableview.delegate('contextmenu', function (e, tableview) { var record = tableview.getRecord(e.currentTarget.get('id')); diff --git a/repository/lib.php b/repository/lib.php index db377d4e7c3ad..b5296d6ce9855 100644 --- a/repository/lib.php +++ b/repository/lib.php @@ -3249,3 +3249,61 @@ function repository_delete_selected_files($context, string $component, string $f 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; +} diff --git a/repository/tests/behat/behat_filepicker.php b/repository/tests/behat/behat_filepicker.php index 58660fd99036a..fc4beb61cffcd 100644 --- a/repository/tests/behat/behat_filepicker.php +++ b/repository/tests/behat/behat_filepicker.php @@ -185,7 +185,7 @@ public function i_delete_file_from_filemanager($name, $filemanagerelement) { */ public function i_mark_for_deletion_from_filemanager($name) { $name = behat_context_helper::escape($name); - $okbutton = $this->find('css', "input.mark-for-deletion[data-fullname=$name]"); + $okbutton = $this->find('css', "input.mark-for-selection[data-fullname=$name]"); $okbutton->click(); }