From 9c07064417fd6089c0bd7cc63a83ff3171f52776 Mon Sep 17 00:00:00 2001 From: Sara Arjona Date: Thu, 18 Mar 2021 12:40:32 +0100 Subject: [PATCH 1/3] MDL-67999 core_contentbank: Add namespace to upload form The form to upload/replace files has been moved to classes/form (it's where it should have been added when it was created). Besides, this class has been renamed because now, with the namespace, contentbank and form don't need to be in the name ;-) --- .../form/upload_files.php} | 16 +++------------- contentbank/upload.php | 4 ++-- 2 files changed, 5 insertions(+), 15 deletions(-) rename contentbank/{files_form.php => classes/form/upload_files.php} (86%) diff --git a/contentbank/files_form.php b/contentbank/classes/form/upload_files.php similarity index 86% rename from contentbank/files_form.php rename to contentbank/classes/form/upload_files.php index 6a001f05675..bcb37d4fa9f 100644 --- a/contentbank/files_form.php +++ b/contentbank/classes/form/upload_files.php @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +namespace core_contentbank\form; + /** * Upload files to content bank form * @@ -21,19 +23,7 @@ * @copyright 2020 Amaia Anabitarte * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - -defined('MOODLE_INTERNAL') || die(); - -require_once("$CFG->libdir/formslib.php"); - -/** - * Class contentbank_files_form - * - * @package core_contentbank - * @copyright 2020 Amaia Anabitarte - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class contentbank_files_form extends moodleform { +class upload_files extends \moodleform { /** * Add elements to this form. diff --git a/contentbank/upload.php b/contentbank/upload.php index 81a7870d044..d4892a344b2 100644 --- a/contentbank/upload.php +++ b/contentbank/upload.php @@ -23,7 +23,6 @@ */ require('../config.php'); -require_once("$CFG->dirroot/contentbank/files_form.php"); use core\output\notification; @@ -86,7 +85,8 @@ $options = array( ); file_prepare_standard_filemanager($data, 'files', $options, $context, 'contentbank', 'public', 0); -$mform = new contentbank_files_form(null, ['contextid' => $contextid, 'data' => $data, 'options' => $options, 'id' => $id]); +$customdata = ['contextid' => $contextid, 'data' => $data, 'options' => $options, 'id' => $id]; +$mform = new \core_contentbank\form\upload_files(null, $customdata); $error = ''; From dcf94b0ca21d9e6e028dd4435dc49a305e4ae2f6 Mon Sep 17 00:00:00 2001 From: Sara Arjona Date: Thu, 18 Mar 2021 12:51:03 +0100 Subject: [PATCH 2/3] MDL-67999 contentbank: Make upload/replace files modal form Once MDL-64554 landed, content bank can implement the original proposal, using a modal form when uploading files. --- contentbank/amd/build/upload.min.js | 2 + contentbank/amd/build/upload.min.js.map | 1 + contentbank/amd/src/upload.js | 52 ++++++ contentbank/classes/form/upload_files.php | 169 ++++++++++++++++-- contentbank/index.php | 9 +- .../templates/bankcontent/toolbar.mustache | 2 +- contentbank/view.php | 10 +- 7 files changed, 226 insertions(+), 19 deletions(-) create mode 100644 contentbank/amd/build/upload.min.js create mode 100644 contentbank/amd/build/upload.min.js.map create mode 100644 contentbank/amd/src/upload.js diff --git a/contentbank/amd/build/upload.min.js b/contentbank/amd/build/upload.min.js new file mode 100644 index 00000000000..9df927d217c --- /dev/null +++ b/contentbank/amd/build/upload.min.js @@ -0,0 +1,2 @@ +define ("core_contentbank/upload",["exports","core_form/modalform","core/str"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.initModal=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);a.initModal=function initModal(a,d,f,g){var h=document.querySelector(a);h.addEventListener("click",function(a){a.preventDefault();var e=new b.default({formClass:d,args:{contextid:f,id:g},modalConfig:{title:(0,c.get_string)("upload","contentbank")},returnFocus:a.target});e.addEventListener(e.events.FORM_SUBMITTED,function(a){document.location=a.detail.returnurl});e.show()})}}); +//# sourceMappingURL=upload.min.js.map diff --git a/contentbank/amd/build/upload.min.js.map b/contentbank/amd/build/upload.min.js.map new file mode 100644 index 00000000000..c14d2d706d4 --- /dev/null +++ b/contentbank/amd/build/upload.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/upload.js"],"names":["initModal","elementSelector","formClass","contextId","contentId","element","document","querySelector","addEventListener","e","preventDefault","form","ModalForm","args","contextid","id","modalConfig","title","returnFocus","target","events","FORM_SUBMITTED","event","location","detail","returnurl","show"],"mappings":"gLAsBA,uD,YAWyB,QAAZA,CAAAA,SAAY,CAACC,CAAD,CAAkBC,CAAlB,CAA6BC,CAA7B,CAAwCC,CAAxC,CAAsD,CAC3E,GAAMC,CAAAA,CAAO,CAAGC,QAAQ,CAACC,aAAT,CAAuBN,CAAvB,CAAhB,CACAI,CAAO,CAACG,gBAAR,CAAyB,OAAzB,CAAkC,SAASC,CAAT,CAAY,CAC1CA,CAAC,CAACC,cAAF,GACA,GAAMC,CAAAA,CAAI,CAAG,GAAIC,UAAJ,CAAc,CACvBV,SAAS,CAATA,CADuB,CAEvBW,IAAI,CAAE,CACFC,SAAS,CAAEX,CADT,CAEFY,EAAE,CAAEX,CAFF,CAFiB,CAMvBY,WAAW,CAAE,CAACC,KAAK,CAAE,iBAAU,QAAV,CAAoB,aAApB,CAAR,CANU,CAOvBC,WAAW,CAAET,CAAC,CAACU,MAPQ,CAAd,CAAb,CASAR,CAAI,CAACH,gBAAL,CAAsBG,CAAI,CAACS,MAAL,CAAYC,cAAlC,CAAkD,SAACC,CAAD,CAAW,CACzDhB,QAAQ,CAACiB,QAAT,CAAoBD,CAAK,CAACE,MAAN,CAAaC,SACpC,CAFD,EAGAd,CAAI,CAACe,IAAL,EACH,CAfD,CAgBH,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module to handle AJAX interactions with content bank upload files.\n *\n * @module core_contentbank/upload\n * @copyright 2021 Sara Arjona \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport ModalForm from 'core_form/modalform';\nimport {get_string as getString} from 'core/str';\n\n/**\n * Initialize upload files to the content bank form as Modal form.\n *\n * @param {String} elementSelector\n * @param {String} formClass\n * @param {Integer} contextId\n * @param {Integer} contentId\n */\nexport const initModal = (elementSelector, formClass, contextId, contentId) => {\n const element = document.querySelector(elementSelector);\n element.addEventListener('click', function(e) {\n e.preventDefault();\n const form = new ModalForm({\n formClass,\n args: {\n contextid: contextId,\n id: contentId,\n },\n modalConfig: {title: getString('upload', 'contentbank')},\n returnFocus: e.target,\n });\n form.addEventListener(form.events.FORM_SUBMITTED, (event) => {\n document.location = event.detail.returnurl;\n });\n form.show();\n });\n};\n"],"file":"upload.min.js"} \ No newline at end of file diff --git a/contentbank/amd/src/upload.js b/contentbank/amd/src/upload.js new file mode 100644 index 00000000000..fb1f7c8b241 --- /dev/null +++ b/contentbank/amd/src/upload.js @@ -0,0 +1,52 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Module to handle AJAX interactions with content bank upload files. + * + * @module core_contentbank/upload + * @copyright 2021 Sara Arjona + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +import ModalForm from 'core_form/modalform'; +import {get_string as getString} from 'core/str'; + +/** + * Initialize upload files to the content bank form as Modal form. + * + * @param {String} elementSelector + * @param {String} formClass + * @param {Integer} contextId + * @param {Integer} contentId + */ +export const initModal = (elementSelector, formClass, contextId, contentId) => { + const element = document.querySelector(elementSelector); + element.addEventListener('click', function(e) { + e.preventDefault(); + const form = new ModalForm({ + formClass, + args: { + contextid: contextId, + id: contentId, + }, + modalConfig: {title: getString('upload', 'contentbank')}, + returnFocus: e.target, + }); + form.addEventListener(form.events.FORM_SUBMITTED, (event) => { + document.location = event.detail.returnurl; + }); + form.show(); + }); +}; diff --git a/contentbank/classes/form/upload_files.php b/contentbank/classes/form/upload_files.php index bcb37d4fa9f..91f8d3a29ca 100644 --- a/contentbank/classes/form/upload_files.php +++ b/contentbank/classes/form/upload_files.php @@ -23,7 +23,7 @@ namespace core_contentbank\form; * @copyright 2020 Amaia Anabitarte * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class upload_files extends \moodleform { +class upload_files extends \core_form\dynamic_form { /** * Add elements to this form. @@ -31,23 +31,15 @@ class upload_files extends \moodleform { public function definition() { $mform = $this->_form; - $mform->addElement('hidden', 'contextid', $this->_customdata['contextid']); + $mform->addElement('hidden', 'contextid'); $mform->setType('contextid', PARAM_INT); - if (!empty($this->_customdata['id'])) { - $mform->addElement('hidden', 'id', $this->_customdata['id']); - $mform->setType('id', PARAM_INT); - } + $mform->addElement('hidden', 'id'); + $mform->setType('id', PARAM_INT); - $options = $this->_customdata['options']; - $mform->addElement('filepicker', 'file', get_string('file', 'core_contentbank'), null, $options); + $mform->addElement('filepicker', 'file', get_string('file', 'core_contentbank'), null, $this->get_options()); $mform->addHelpButton('file', 'file', 'core_contentbank'); $mform->addRule('file', null, 'required'); - - $this->add_action_buttons(true, get_string('savechanges')); - - $data = $this->_customdata['data']; - $this->set_data($data); } /** @@ -60,9 +52,158 @@ class upload_files extends \moodleform { public function validation($data, $files) { $errors = array(); $draftitemid = $data['file']; - if (file_is_draft_area_limit_reached($draftitemid, $this->_customdata['options']['areamaxbytes'])) { + $options = $this->get_options(); + if (file_is_draft_area_limit_reached($draftitemid, $options['areamaxbytes'])) { $errors['file'] = get_string('userquotalimit', 'error'); } return $errors; } + + /** + * Check if current user has access to this form, otherwise throw exception + * + * Sometimes permission check may depend on the action and/or id of the entity. + * If necessary, form data is available in $this->_ajaxformdata or + * by calling $this->optional_param() + */ + protected function check_access_for_dynamic_submission(): void { + require_capability('moodle/contentbank:upload', $this->get_context_for_dynamic_submission()); + + // Check the context used by the content bank is allowed. + $cb = new \core_contentbank\contentbank(); + if (!$cb->is_context_allowed($this->get_context_for_dynamic_submission())) { + throw new \moodle_exception('contextnotallowed', 'core_contentbank'); + } + + // If $id is defined, the file content will be replaced (instead of uploading a new one). + // Check that the user has the right permissions to replace this content file. + $id = $this->optional_param('id', null, PARAM_INT); + if ($id) { + $content = $cb->get_content_from_id($id); + $contenttype = $content->get_content_type_instance(); + if (!$contenttype->can_manage($content) || !$contenttype->can_upload()) { + throw new \moodle_exception('nopermissions', 'error', '', null, get_string('replacecontent', 'contentbank')); + } + } + } + + /** + * Returns form context + * + * If context depends on the form data, it is available in $this->_ajaxformdata or + * by calling $this->optional_param() + * + * @return \context + */ + protected function get_context_for_dynamic_submission(): \context { + $contextid = $this->optional_param('contextid', null, PARAM_INT); + return \context::instance_by_id($contextid, MUST_EXIST); + } + + /** + * File upload options + * + * @return array + * @throws \coding_exception + */ + protected function get_options(): array { + global $CFG; + + $maxbytes = $CFG->userquota; + $maxareabytes = $CFG->userquota; + if (has_capability('moodle/user:ignoreuserquota', $this->get_context_for_dynamic_submission())) { + $maxbytes = USER_CAN_IGNORE_FILE_SIZE_LIMITS; + $maxareabytes = FILE_AREA_MAX_BYTES_UNLIMITED; + } + + $cb = new \core_contentbank\contentbank(); + $id = $this->optional_param('id', null, PARAM_INT); + if ($id) { + $content = $cb->get_content_from_id($id); + $contenttype = $content->get_content_type_instance(); + $extensions = $contenttype->get_manageable_extensions(); + $acceptedtypes = implode(',', $extensions); + } else { + $acceptedtypes = $cb->get_supported_extensions_as_string($this->get_context_for_dynamic_submission()); + } + + return ['subdirs' => 1, 'maxbytes' => $maxbytes, 'maxfiles' => -1, 'accepted_types' => $acceptedtypes, + 'areamaxbytes' => $maxareabytes]; + } + + /** + * Process the form submission, used if form was submitted via AJAX + * + * This method can return scalar values or arrays that can be json-encoded, they will be passed to the caller JS. + * + * Submission data can be accessed as: $this->get_data() + * + * @return mixed + */ + public function process_dynamic_submission() { + global $USER; + + // Get the file and create the content based on it. + $usercontext = \context_user::instance($USER->id); + $fs = get_file_storage(); + $files = $fs->get_area_files($usercontext->id, 'user', 'draft', $this->get_data()->file, 'itemid, filepath, + filename', false); + if (!empty($files)) { + $file = reset($files); + $cb = new \core_contentbank\contentbank(); + if ($this->get_data()->id) { + $content = $cb->get_content_from_id($this->get_data()->id); + $contenttype = $content->get_content_type_instance(); + $content = $contenttype->replace_content($file, $content); + } else { + $content = $cb->create_content_from_file($this->get_context_for_dynamic_submission(), $USER->id, $file); + } + $params = ['id' => $content->get_id(), 'contextid' => $this->get_context_for_dynamic_submission()->id]; + $viewurl = new \moodle_url('/contentbank/view.php', $params); + return ['returnurl' => $viewurl->out(false)]; + } + + return null; + } + + /** + * Load in existing data as form defaults + * + * Can be overridden to retrieve existing values from db by entity id and also + * to preprocess editor and filemanager elements + * + * Example: + * $this->set_data(get_entity($this->_ajaxformdata['id'])); + */ + public function set_data_for_dynamic_submission(): void { + $data = (object)[ + 'contextid' => $this->optional_param('contextid', null, PARAM_INT), + 'id' => $this->optional_param('id', null, PARAM_INT), + ]; + $this->set_data($data); + } + + /** + * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX + * + * This is used in the form elements sensitive to the page url, such as Atto autosave in 'editor' + * + * If the form has arguments (such as 'id' of the element being edited), the URL should + * also have respective argument. + * + * @return \moodle_url + */ + protected function get_page_url_for_dynamic_submission(): \moodle_url { + $params = ['contextid' => $this->get_context_for_dynamic_submission()->id]; + + $id = $this->optional_param('id', null, PARAM_INT); + if ($id) { + $url = '/contentbank/view.php'; + $params['id'] = $id; + } else { + $url = '/contentbank/index.php'; + } + + return new \moodle_url($url, $params); + } } diff --git a/contentbank/index.php b/contentbank/index.php index c4d22428d9f..7845f58ecd4 100644 --- a/contentbank/index.php +++ b/contentbank/index.php @@ -88,13 +88,18 @@ if (has_capability('moodle/contentbank:upload', $context)) { // Don' show upload button if there's no plugin to support any file extension. $accepted = $cb->get_supported_extensions_as_string($context); if (!empty($accepted)) { - $importurl = new moodle_url('/contentbank/upload.php', ['contextid' => $contextid]); + $importurl = new moodle_url('/contentbank/index.php', ['contextid' => $contextid]); $toolbar[] = [ 'name' => get_string('upload', 'contentbank'), - 'link' => $importurl, + 'link' => $importurl->out(false), 'icon' => 'i/upload', 'action' => 'upload' ]; + $PAGE->requires->js_call_amd( + 'core_contentbank/upload', + 'initModal', + ['[data-action=upload]', \core_contentbank\form\upload_files::class, $contextid] + ); } } diff --git a/contentbank/templates/bankcontent/toolbar.mustache b/contentbank/templates/bankcontent/toolbar.mustache index 4d590ce5061..d1fbb7db100 100644 --- a/contentbank/templates/bankcontent/toolbar.mustache +++ b/contentbank/templates/bankcontent/toolbar.mustache @@ -59,7 +59,7 @@ {{>core_contentbank/bankcontent/toolbar_dropdown}} {{/dropdown}} {{^dropdown}} - + {{#pix}} {{{ icon }}} {{/pix}} {{{ name }}} {{/dropdown}} diff --git a/contentbank/view.php b/contentbank/view.php index 8258fb2db09..48519f4fc21 100644 --- a/contentbank/view.php +++ b/contentbank/view.php @@ -123,11 +123,17 @@ if ($contenttype->can_manage($content)) { if ($contenttype->can_upload()) { $actionmenu->add_secondary_action(new action_menu_link( - new moodle_url('/contentbank/upload.php', ['contextid' => $context->id, 'id' => $content->get_id()]), + new moodle_url('/contentbank/view.php', ['contextid' => $context->id, 'id' => $content->get_id()]), new pix_icon('i/upload', get_string('upload')), get_string('replacecontent', 'contentbank'), - false + false, + ['data-action' => 'upload'] )); + $PAGE->requires->js_call_amd( + 'core_contentbank/upload', + 'initModal', + ['[data-action=upload]', \core_contentbank\form\upload_files::class, $context->id, $content->get_id()] + ); } } if ($contenttype->can_download($content)) { From e5872a3b4583b42c9075a2222c113210727bbab5 Mon Sep 17 00:00:00 2001 From: Sara Arjona Date: Thu, 18 Mar 2021 16:30:02 +0100 Subject: [PATCH 3/3] MDL-67999 contentbank: Remove contentbank/upload.php As the form to upload files to the content bank has been moved to modal, this file is not required any more, so it has been removed. --- contentbank/upgrade.txt | 2 + contentbank/upload.php | 125 ---------------------------------------- 2 files changed, 2 insertions(+), 125 deletions(-) delete mode 100644 contentbank/upload.php diff --git a/contentbank/upgrade.txt b/contentbank/upgrade.txt index 8eca72b6fa0..aa7d8edf3f7 100644 --- a/contentbank/upgrade.txt +++ b/contentbank/upgrade.txt @@ -4,3 +4,5 @@ information provided here is intended especially for developers. === 3.11 === * Added "get_uses()" method to content class to return places where a content is used. * Added set_visibility()/get_visibility() methods to let users decide if their content should be listed in the content bank. +* The contentbank/upload.php page for displaying the upload files form has been removed. The form for uploading/replacing +files now is displayed in a modal. diff --git a/contentbank/upload.php b/contentbank/upload.php deleted file mode 100644 index d4892a344b2..00000000000 --- a/contentbank/upload.php +++ /dev/null @@ -1,125 +0,0 @@ -. - -/** - * Upload a file to content bank. - * - * @package core_contentbank - * @copyright 2020 Amaia Anabitarte - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -require('../config.php'); - -use core\output\notification; - -require_login(); - -$contextid = optional_param('contextid', \context_system::instance()->id, PARAM_INT); -$context = context::instance_by_id($contextid, MUST_EXIST); - -$cb = new \core_contentbank\contentbank(); -if (!$cb->is_context_allowed($context)) { - print_error('contextnotallowed', 'core_contentbank'); -} - -require_capability('moodle/contentbank:upload', $context); - -$id = optional_param('id', null, PARAM_INT); -if ($id) { - $content = $cb->get_content_from_id($id); - $contenttype = $content->get_content_type_instance(); - if (!$contenttype->can_manage($content) || !$contenttype->can_upload()) { - print_error('nopermissions', 'error', $returnurl, get_string('replacecontent', 'contentbank')); - } -} - -$title = get_string('contentbank'); -\core_contentbank\helper::get_page_ready($context, $title, true); -if ($PAGE->course) { - require_login($PAGE->course->id); -} -$returnurl = new \moodle_url('/contentbank/index.php', ['contextid' => $contextid]); - -$PAGE->set_url('/contentbank/upload.php'); -$PAGE->set_context($context); -$PAGE->navbar->add(get_string('upload', 'contentbank')); -$PAGE->set_title($title); -$PAGE->set_heading($title); -$PAGE->set_pagetype('contentbank'); - -$maxbytes = $CFG->userquota; -$maxareabytes = $CFG->userquota; -if (has_capability('moodle/user:ignoreuserquota', $context)) { - $maxbytes = USER_CAN_IGNORE_FILE_SIZE_LIMITS; - $maxareabytes = FILE_AREA_MAX_BYTES_UNLIMITED; -} - -if ($id) { - $extensions = $contenttype->get_manageable_extensions(); - $accepted = implode(',', $extensions); -} else { - $accepted = $cb->get_supported_extensions_as_string($context); -} - -$data = new stdClass(); -$options = array( - 'subdirs' => 1, - 'maxbytes' => $maxbytes, - 'maxfiles' => -1, - 'accepted_types' => $accepted, - 'areamaxbytes' => $maxareabytes -); -file_prepare_standard_filemanager($data, 'files', $options, $context, 'contentbank', 'public', 0); - -$customdata = ['contextid' => $contextid, 'data' => $data, 'options' => $options, 'id' => $id]; -$mform = new \core_contentbank\form\upload_files(null, $customdata); - -$error = ''; - -if ($mform->is_cancelled()) { - redirect($returnurl); -} else if ($formdata = $mform->get_data()) { - require_sesskey(); - // Get the file and create the content based on it. - $usercontext = \context_user::instance($USER->id); - $fs = get_file_storage(); - $files = $fs->get_area_files($usercontext->id, 'user', 'draft', $formdata->file, 'itemid, filepath, filename', false); - if (!empty($files)) { - $file = reset($files); - if ($id) { - $content = $contenttype->replace_content($file, $content); - } else { - $content = $cb->create_content_from_file($context, $USER->id, $file); - } - $viewurl = new \moodle_url('/contentbank/view.php', ['id' => $content->get_id(), 'contextid' => $contextid]); - redirect($viewurl); - } else { - $error = get_string('errornofile', 'contentbank'); - } -} - -echo $OUTPUT->header(); -echo $OUTPUT->box_start('generalbox'); - -if (!empty($error)) { - echo $OUTPUT->notification($error, notification::NOTIFY_ERROR); -} - -$mform->display(); - -echo $OUTPUT->box_end(); -echo $OUTPUT->footer();