From 448012c6b601a29d221ce51498dcdcdf5df91705 Mon Sep 17 00:00:00 2001 From: Amaia Anabitarte Date: Fri, 17 Apr 2020 13:40:55 +0200 Subject: [PATCH 1/3] MDL-67790 core_contentbank: Add Rename content option --- contentbank/amd/build/actions.min.js | 2 +- contentbank/amd/build/actions.min.js.map | 2 +- contentbank/amd/src/actions.js | 92 ++++++++++++ contentbank/classes/content.php | 28 ++++ contentbank/classes/contenttype.php | 50 ++++++- .../classes/external/rename_content.php | 136 ++++++++++++++++++ contentbank/templates/renamecontent.mustache | 30 ++++ contentbank/view.php | 53 +++++-- lang/en/contentbank.php | 8 +- lang/en/role.php | 4 +- lib/db/access.php | 22 +++ lib/db/services.php | 9 ++ version.php | 2 +- 13 files changed, 417 insertions(+), 21 deletions(-) create mode 100644 contentbank/classes/external/rename_content.php create mode 100644 contentbank/templates/renamecontent.mustache diff --git a/contentbank/amd/build/actions.min.js b/contentbank/amd/build/actions.min.js index 2e95d9ad9e3..4d78f539037 100644 --- a/contentbank/amd/build/actions.min.js +++ b/contentbank/amd/build/actions.min.js @@ -1,2 +1,2 @@ -define ("core_contentbank/actions",["jquery","core/ajax","core/notification","core/str","core/templates","core/url","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f,g,h){var j={DELETE_CONTENT:"[data-action=\"deletecontent\"]"},k=function(){this.registerEvents()};k.prototype.registerEvents=function(){a(j.DELETE_CONTENT).click(function(b){b.preventDefault();var e=a(this).data("contentname"),f=a(this).data("contentid"),j=a(this).data("contextid"),k="";d.get_strings([{key:"deletecontent",component:"core_contentbank"},{key:"deletecontentconfirm",component:"core_contentbank",param:{name:e}},{key:"delete",component:"core"}]).then(function(a){var b=a[0],c=a[1];k=a[2];return g.create({title:b,body:c,type:g.types.SAVE_CANCEL,large:!0})}).done(function(a){a.setSaveButtonText(k);a.getRoot().on(h.save,function(){return i(f,j)});a.getRoot().on(h.hidden,function(){a.destroy()});a.show()}).catch(c.exception)})};function i(a,e){var g="success";b.call([{methodname:"core_contentbank_delete_content",args:{contentids:{contentid:a}}}])[0].then(function(a){if(a.result){return d.get_string("contentdeleted","core_contentbank")}g="error";return d.get_string("contentnotdeleted","core_contentbank")}).done(function(a){var b={contextid:e};if("success"==g){b.statusmsg=a}else{b.errormsg=a}window.location.href=f.relativeUrl("contentbank/index.php",b,!1)}).fail(c.exception)}return{init:function init(){return new k}}}); +define ("core_contentbank/actions",["jquery","core/ajax","core/notification","core/str","core/templates","core/url","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f,g,h){var k={DELETE_CONTENT:"[data-action=\"deletecontent\"]",RENAME_CONTENT:"[data-action=\"renamecontent\"]"},l=function(){this.registerEvents()};l.prototype.registerEvents=function(){a(k.DELETE_CONTENT).click(function(b){b.preventDefault();var e=a(this).data("contentname"),f=a(this).data("contentid"),j=a(this).data("contextid"),k="";d.get_strings([{key:"deletecontent",component:"core_contentbank"},{key:"deletecontentconfirm",component:"core_contentbank",param:{name:e}},{key:"delete",component:"core"}]).then(function(a){var b=a[0],c=a[1];k=a[2];return g.create({title:b,body:c,type:g.types.SAVE_CANCEL,large:!0})}).done(function(a){a.setSaveButtonText(k);a.getRoot().on(h.save,function(){return i(f,j)});a.getRoot().on(h.hidden,function(){a.destroy()});a.show()}).catch(c.exception)});a(k.RENAME_CONTENT).click(function(b){b.preventDefault();var f=a(this).data("contentname"),i=a(this).data("contentid"),k="";d.get_strings([{key:"renamecontent",component:"core_contentbank"},{key:"rename",component:"core_contentbank"}]).then(function(a){var b=a[0];k=a[1];return g.create({title:b,body:e.render("core_contentbank/renamecontent",{contentid:i,name:f}),type:g.types.SAVE_CANCEL})}).then(function(b){b.setSaveButtonText(k);b.getRoot().on(h.save,function(){var b=a("#newname").val();return j(i,b)});b.getRoot().on(h.hidden,function(){b.destroy()});b.show()}).catch(c.exception)})};function i(a,e){var g="success";b.call([{methodname:"core_contentbank_delete_content",args:{contentids:{contentid:a}}}])[0].then(function(a){if(a.result){return d.get_string("contentdeleted","core_contentbank")}g="error";return d.get_string("contentnotdeleted","core_contentbank")}).done(function(a){var b={contextid:e};if("success"==g){b.statusmsg=a}else{b.errormsg=a}window.location.href=f.relativeUrl("contentbank/index.php",b,!1)}).fail(c.exception)}function j(a,e){var g="success";b.call([{methodname:"core_contentbank_rename_content",args:{contentid:a,name:e}}])[0].then(function(a){if(a){return d.get_string("contentrenamed","core_contentbank")}g="error";return d.get_string("contentnotrenamed","core_contentbank")}).then(function(b){var d=null;if("success"==g){d={id:a,statusmsg:b};window.location.href=f.relativeUrl("contentbank/view.php",d,!1)}else{c.addNotification({message:b,type:"error"});c.fetchNotifications()}}).catch(c.exception)}return{init:function init(){return new l}}}); //# sourceMappingURL=actions.min.js.map diff --git a/contentbank/amd/build/actions.min.js.map b/contentbank/amd/build/actions.min.js.map index 05f46fb879d..6e5344eaf8c 100644 --- a/contentbank/amd/build/actions.min.js.map +++ b/contentbank/amd/build/actions.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/actions.js"],"names":["define","$","Ajax","Notification","Str","Templates","Url","ModalFactory","ModalEvents","ACTIONS","DELETE_CONTENT","Actions","registerEvents","prototype","click","e","preventDefault","contentname","data","contentid","contextid","deleteButtonText","get_strings","key","component","param","name","then","langStrings","modalTitle","modalContent","create","title","body","type","types","SAVE_CANCEL","large","done","modal","setSaveButtonText","getRoot","on","save","deleteContent","hidden","destroy","show","catch","exception","requestType","call","methodname","args","contentids","result","get_string","message","params","statusmsg","errormsg","window","location","href","relativeUrl","fail"],"mappings":"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","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 manage content bank actions, such as delete or rename.\n *\n * @module core_contentbank/actions\n * @package core_contentbank\n * @copyright 2020 Sara Arjona \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([\n 'jquery',\n 'core/ajax',\n 'core/notification',\n 'core/str',\n 'core/templates',\n 'core/url',\n 'core/modal_factory',\n 'core/modal_events'],\nfunction($, Ajax, Notification, Str, Templates, Url, ModalFactory, ModalEvents) {\n\n /**\n * List of action selectors.\n *\n * @type {{DELETE_CONTENT: string}}\n */\n var ACTIONS = {\n DELETE_CONTENT: '[data-action=\"deletecontent\"]',\n };\n\n /**\n * Actions class.\n */\n var Actions = function() {\n this.registerEvents();\n };\n\n /**\n * Register event listeners.\n */\n Actions.prototype.registerEvents = function() {\n $(ACTIONS.DELETE_CONTENT).click(function(e) {\n e.preventDefault();\n\n var contentname = $(this).data('contentname');\n var contentid = $(this).data('contentid');\n var contextid = $(this).data('contextid');\n\n var strings = [\n {\n key: 'deletecontent',\n component: 'core_contentbank'\n },\n {\n key: 'deletecontentconfirm',\n component: 'core_contentbank',\n param: {\n name: contentname,\n }\n },\n {\n key: 'delete',\n component: 'core'\n },\n ];\n\n var deleteButtonText = '';\n Str.get_strings(strings).then(function(langStrings) {\n var modalTitle = langStrings[0];\n var modalContent = langStrings[1];\n deleteButtonText = langStrings[2];\n\n return ModalFactory.create({\n title: modalTitle,\n body: modalContent,\n type: ModalFactory.types.SAVE_CANCEL,\n large: true\n });\n }).done(function(modal) {\n modal.setSaveButtonText(deleteButtonText);\n modal.getRoot().on(ModalEvents.save, function() {\n // The action is now confirmed, sending an action for it.\n return deleteContent(contentid, contextid);\n });\n\n // Handle hidden event.\n modal.getRoot().on(ModalEvents.hidden, function() {\n // Destroy when hidden.\n modal.destroy();\n });\n\n // Show the modal.\n modal.show();\n\n return;\n }).catch(Notification.exception);\n });\n };\n\n /**\n * Delete content from the content bank.\n *\n * @param {int} contentid The content to delete.\n * @param {int} contextid The contextid where the content belongs.\n */\n function deleteContent(contentid, contextid) {\n var request = {\n methodname: 'core_contentbank_delete_content',\n args: {\n contentids: {contentid}\n }\n };\n\n var requestType = 'success';\n Ajax.call([request])[0].then(function(data) {\n if (data.result) {\n return Str.get_string('contentdeleted', 'core_contentbank');\n }\n requestType = 'error';\n return Str.get_string('contentnotdeleted', 'core_contentbank');\n\n }).done(function(message) {\n var params = {\n contextid: contextid\n };\n if (requestType == 'success') {\n params.statusmsg = message;\n } else {\n params.errormsg = message;\n }\n // Redirect to the main content bank page and display the message as a notification.\n window.location.href = Url.relativeUrl('contentbank/index.php', params, false);\n }).fail(Notification.exception);\n }\n\n return /** @alias module:core_contentbank/actions */ {\n // Public variables and functions.\n\n /**\n * Initialise the contentbank actions.\n *\n * @method init\n * @return {Actions}\n */\n 'init': function() {\n return new Actions();\n }\n };\n});\n"],"file":"actions.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/actions.js"],"names":["define","$","Ajax","Notification","Str","Templates","Url","ModalFactory","ModalEvents","ACTIONS","DELETE_CONTENT","RENAME_CONTENT","Actions","registerEvents","prototype","click","e","preventDefault","contentname","data","contentid","contextid","deleteButtonText","get_strings","key","component","param","name","then","langStrings","modalTitle","modalContent","create","title","body","type","types","SAVE_CANCEL","large","done","modal","setSaveButtonText","getRoot","on","save","deleteContent","hidden","destroy","show","catch","exception","saveButtonText","render","newname","val","renameContent","requestType","call","methodname","args","contentids","result","get_string","message","params","statusmsg","errormsg","window","location","href","relativeUrl","fail","id","addNotification","fetchNotifications"],"mappings":"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","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 manage content bank actions, such as delete or rename.\n *\n * @module core_contentbank/actions\n * @package core_contentbank\n * @copyright 2020 Sara Arjona \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([\n 'jquery',\n 'core/ajax',\n 'core/notification',\n 'core/str',\n 'core/templates',\n 'core/url',\n 'core/modal_factory',\n 'core/modal_events'],\nfunction($, Ajax, Notification, Str, Templates, Url, ModalFactory, ModalEvents) {\n\n /**\n * List of action selectors.\n *\n * @type {{DELETE_CONTENT: string}}\n */\n var ACTIONS = {\n DELETE_CONTENT: '[data-action=\"deletecontent\"]',\n RENAME_CONTENT: '[data-action=\"renamecontent\"]',\n };\n\n /**\n * Actions class.\n */\n var Actions = function() {\n this.registerEvents();\n };\n\n /**\n * Register event listeners.\n */\n Actions.prototype.registerEvents = function() {\n $(ACTIONS.DELETE_CONTENT).click(function(e) {\n e.preventDefault();\n\n var contentname = $(this).data('contentname');\n var contentid = $(this).data('contentid');\n var contextid = $(this).data('contextid');\n\n var strings = [\n {\n key: 'deletecontent',\n component: 'core_contentbank'\n },\n {\n key: 'deletecontentconfirm',\n component: 'core_contentbank',\n param: {\n name: contentname,\n }\n },\n {\n key: 'delete',\n component: 'core'\n },\n ];\n\n var deleteButtonText = '';\n Str.get_strings(strings).then(function(langStrings) {\n var modalTitle = langStrings[0];\n var modalContent = langStrings[1];\n deleteButtonText = langStrings[2];\n\n return ModalFactory.create({\n title: modalTitle,\n body: modalContent,\n type: ModalFactory.types.SAVE_CANCEL,\n large: true\n });\n }).done(function(modal) {\n modal.setSaveButtonText(deleteButtonText);\n modal.getRoot().on(ModalEvents.save, function() {\n // The action is now confirmed, sending an action for it.\n return deleteContent(contentid, contextid);\n });\n\n // Handle hidden event.\n modal.getRoot().on(ModalEvents.hidden, function() {\n // Destroy when hidden.\n modal.destroy();\n });\n\n // Show the modal.\n modal.show();\n\n return;\n }).catch(Notification.exception);\n });\n\n $(ACTIONS.RENAME_CONTENT).click(function(e) {\n e.preventDefault();\n\n var contentname = $(this).data('contentname');\n var contentid = $(this).data('contentid');\n\n var strings = [\n {\n key: 'renamecontent',\n component: 'core_contentbank'\n },\n {\n key: 'rename',\n component: 'core_contentbank'\n },\n ];\n\n var saveButtonText = '';\n Str.get_strings(strings).then(function(langStrings) {\n var modalTitle = langStrings[0];\n saveButtonText = langStrings[1];\n\n return ModalFactory.create({\n title: modalTitle,\n body: Templates.render('core_contentbank/renamecontent', {'contentid': contentid, 'name': contentname}),\n type: ModalFactory.types.SAVE_CANCEL\n });\n }).then(function(modal) {\n modal.setSaveButtonText(saveButtonText);\n modal.getRoot().on(ModalEvents.save, function() {\n // The action is now confirmed, sending an action for it.\n var newname = $(\"#newname\").val();\n return renameContent(contentid, newname);\n });\n\n // Handle hidden event.\n modal.getRoot().on(ModalEvents.hidden, function() {\n // Destroy when hidden.\n modal.destroy();\n });\n\n // Show the modal.\n modal.show();\n\n return;\n }).catch(Notification.exception);\n });\n };\n\n /**\n * Delete content from the content bank.\n *\n * @param {int} contentid The content to delete.\n * @param {int} contextid The contextid where the content belongs.\n */\n function deleteContent(contentid, contextid) {\n var request = {\n methodname: 'core_contentbank_delete_content',\n args: {\n contentids: {contentid}\n }\n };\n\n var requestType = 'success';\n Ajax.call([request])[0].then(function(data) {\n if (data.result) {\n return Str.get_string('contentdeleted', 'core_contentbank');\n }\n requestType = 'error';\n return Str.get_string('contentnotdeleted', 'core_contentbank');\n\n }).done(function(message) {\n var params = {\n contextid: contextid\n };\n if (requestType == 'success') {\n params.statusmsg = message;\n } else {\n params.errormsg = message;\n }\n // Redirect to the main content bank page and display the message as a notification.\n window.location.href = Url.relativeUrl('contentbank/index.php', params, false);\n }).fail(Notification.exception);\n }\n\n /**\n * Rename content in the content bank.\n *\n * @param {int} contentid The content to rename.\n * @param {string} name The new name for the content.\n */\n function renameContent(contentid, name) {\n var request = {\n methodname: 'core_contentbank_rename_content',\n args: {\n contentid: contentid,\n name: name\n }\n };\n var requestType = 'success';\n Ajax.call([request])[0].then(function(data) {\n if (data) {\n return Str.get_string('contentrenamed', 'core_contentbank');\n }\n requestType = 'error';\n return Str.get_string('contentnotrenamed', 'core_contentbank');\n\n }).then(function(message) {\n var params = null;\n if (requestType == 'success') {\n params = {\n id: contentid,\n statusmsg: message\n };\n // Redirect to the content view page and display the message as a notification.\n window.location.href = Url.relativeUrl('contentbank/view.php', params, false);\n } else {\n // Fetch error notifications.\n Notification.addNotification({\n message: message,\n type: 'error'\n });\n Notification.fetchNotifications();\n }\n return;\n }).catch(Notification.exception);\n }\n\n return /** @alias module:core_contentbank/actions */ {\n // Public variables and functions.\n\n /**\n * Initialise the contentbank actions.\n *\n * @method init\n * @return {Actions}\n */\n 'init': function() {\n return new Actions();\n }\n };\n});\n"],"file":"actions.min.js"} \ No newline at end of file diff --git a/contentbank/amd/src/actions.js b/contentbank/amd/src/actions.js index caa25c53780..561cb998341 100644 --- a/contentbank/amd/src/actions.js +++ b/contentbank/amd/src/actions.js @@ -39,6 +39,7 @@ function($, Ajax, Notification, Str, Templates, Url, ModalFactory, ModalEvents) */ var ACTIONS = { DELETE_CONTENT: '[data-action="deletecontent"]', + RENAME_CONTENT: '[data-action="renamecontent"]', }; /** @@ -108,6 +109,54 @@ function($, Ajax, Notification, Str, Templates, Url, ModalFactory, ModalEvents) return; }).catch(Notification.exception); }); + + $(ACTIONS.RENAME_CONTENT).click(function(e) { + e.preventDefault(); + + var contentname = $(this).data('contentname'); + var contentid = $(this).data('contentid'); + + var strings = [ + { + key: 'renamecontent', + component: 'core_contentbank' + }, + { + key: 'rename', + component: 'core_contentbank' + }, + ]; + + var saveButtonText = ''; + Str.get_strings(strings).then(function(langStrings) { + var modalTitle = langStrings[0]; + saveButtonText = langStrings[1]; + + return ModalFactory.create({ + title: modalTitle, + body: Templates.render('core_contentbank/renamecontent', {'contentid': contentid, 'name': contentname}), + type: ModalFactory.types.SAVE_CANCEL + }); + }).then(function(modal) { + modal.setSaveButtonText(saveButtonText); + modal.getRoot().on(ModalEvents.save, function() { + // The action is now confirmed, sending an action for it. + var newname = $("#newname").val(); + return renameContent(contentid, newname); + }); + + // Handle hidden event. + modal.getRoot().on(ModalEvents.hidden, function() { + // Destroy when hidden. + modal.destroy(); + }); + + // Show the modal. + modal.show(); + + return; + }).catch(Notification.exception); + }); }; /** @@ -146,6 +195,49 @@ function($, Ajax, Notification, Str, Templates, Url, ModalFactory, ModalEvents) }).fail(Notification.exception); } + /** + * Rename content in the content bank. + * + * @param {int} contentid The content to rename. + * @param {string} name The new name for the content. + */ + function renameContent(contentid, name) { + var request = { + methodname: 'core_contentbank_rename_content', + args: { + contentid: contentid, + name: name + } + }; + var requestType = 'success'; + Ajax.call([request])[0].then(function(data) { + if (data) { + return Str.get_string('contentrenamed', 'core_contentbank'); + } + requestType = 'error'; + return Str.get_string('contentnotrenamed', 'core_contentbank'); + + }).then(function(message) { + var params = null; + if (requestType == 'success') { + params = { + id: contentid, + statusmsg: message + }; + // Redirect to the content view page and display the message as a notification. + window.location.href = Url.relativeUrl('contentbank/view.php', params, false); + } else { + // Fetch error notifications. + Notification.addNotification({ + message: message, + type: 'error' + }); + Notification.fetchNotifications(); + } + return; + }).catch(Notification.exception); + } + return /** @alias module:core_contentbank/actions */ { // Public variables and functions. diff --git a/contentbank/classes/content.php b/contentbank/classes/content.php index a77b7a08e84..27975cb61f4 100644 --- a/contentbank/classes/content.php +++ b/contentbank/classes/content.php @@ -24,6 +24,7 @@ namespace core_contentbank; +use core_text; use stored_file; use stdClass; use coding_exception; @@ -102,6 +103,33 @@ abstract class content { return $DB->update_record('contentbank_content', $this->content); } + /** + * Set a new name to the content. + * + * @param string $name The name of the content. + * @return bool True if the content has been succesfully updated. False otherwise. + * @throws \coding_exception if not loaded. + */ + public function set_name(string $name): bool { + if (empty($name)) { + return false; + } + + // Clean name. + $name = clean_param($name, PARAM_TEXT); + if (core_text::strlen($name) > 255) { + $name = core_text::substr($name, 0, 255); + } + + $oldname = $this->content->name; + $this->content->name = $name; + $updated = $this->update_content(); + if (!$updated) { + $this->content->name = $oldname; + } + return $updated; + } + /** * Returns the name of the content. * diff --git a/contentbank/classes/contenttype.php b/contentbank/classes/contenttype.php index 46e92a914bf..05922b7e01c 100644 --- a/contentbank/classes/contenttype.php +++ b/contentbank/classes/contenttype.php @@ -24,7 +24,6 @@ namespace core_contentbank; -use coding_exception; use moodle_url; /** @@ -39,7 +38,7 @@ abstract class contenttype { /** Plugin implements uploading feature */ const CAN_UPLOAD = 'upload'; - /** @var context This content's context. **/ + /** @var context This contenttype's context. **/ protected $context = null; /** @@ -99,6 +98,18 @@ abstract class contenttype { return $DB->delete_records('contentbank_content', ['id' => $content->get_id()]); } + /** + * Rename this content from the content_bank. + * This method can be overwritten by the plugins if they need to change some other specific information. + * + * @param content $content The content to rename. + * @param string $name The name of the content. + * @return boolean true if the content has been renamed; false otherwise. + */ + public function rename_content(content $content, string $name): bool { + return $content->set_name($name); + } + /** * Returns the contenttype name of this content. * @@ -240,6 +251,41 @@ abstract class contenttype { return true; } + /** + * Check if the user can managed this content. + * + * @param content $content The content to be managed. + * @return bool True if content could be managed. False otherwise. + */ + public final function can_manage(content $content): bool { + global $USER; + + if ($this->context->id != $content->get_content()->contextid) { + // The content has to have exactly the same context as this contenttype. + return false; + } + + // Check main contentbank management permission. + $hascapability = has_capability('moodle/contentbank:manageanycontent', $this->context); + if ($content->get_content()->usercreated == $USER->id) { + // This content has been created by the current user; check if they can manage their content. + $hascapability = $hascapability || has_capability('moodle/contentbank:manageowncontent', $this->context); + } + + return $hascapability && $this->is_manage_allowed($content); + } + + /** + * Returns if content allows managing. + * + * @param content $content The content to be managed. + * @return bool True if content allows uploading. False otherwise. + */ + protected function is_manage_allowed(content $content): bool { + // Plugins can overwrite this function to add any check they need. + return true; + } + /** * Returns the plugin supports the feature. * diff --git a/contentbank/classes/external/rename_content.php b/contentbank/classes/external/rename_content.php new file mode 100644 index 00000000000..18d36f32347 --- /dev/null +++ b/contentbank/classes/external/rename_content.php @@ -0,0 +1,136 @@ +. + +/** + * External API to rename content bank content. + * + * @package core_contentbank + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_contentbank\external; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . '/externallib.php'); + +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use external_warnings; + +/** + * This is the external method for renaming a content. + * + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class rename_content extends external_api { + /** + * rename_content parameters. + * + * @since Moodle 3.9 + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters( + [ + 'contentid' => new external_value(PARAM_INT, 'The content id to rename', VALUE_REQUIRED), + 'name' => new external_value(PARAM_RAW, 'The new name for the content', VALUE_REQUIRED), + ] + ); + } + + /** + * Rename content from the contentbank. + * + * @since Moodle 3.9 + * @param int $contentid The content id to rename. + * @param string $name The new name. + * @return array True if the content has been renamed; false and the warning, otherwise. + */ + public static function execute(int $contentid, string $name): array { + global $DB; + + $result = false; + $warnings = []; + + $params = self::validate_parameters(self::execute_parameters(), [ + 'contentid' => $contentid, + 'name' => $name, + ]); + $params['name'] = clean_param($params['name'], PARAM_TEXT); + try { + $record = $DB->get_record('contentbank_content', ['id' => $contentid], '*', MUST_EXIST); + $contenttypeclass = "\\$record->contenttype\\contenttype"; + if (class_exists($contenttypeclass)) { + $context = \context::instance_by_id($record->contextid, MUST_EXIST); + self::validate_context($context); + $contenttype = new $contenttypeclass($context); + $contentclass = "\\$record->contenttype\\content"; + $content = new $contentclass($record); + // Check capability. + if ($contenttype->can_manage($content)) { + // This content can be renamed. + if ($contenttype->rename_content($content, $params['name'])) { + $result = true; + } else { + $warnings[] = [ + 'item' => $contentid, + 'warningcode' => 'contentnotrenamed', + 'message' => get_string('contentnotrenamed', 'core_contentbank') + ]; + } + } else { + // The user has no permission to manage this content. + $warnings[] = [ + 'item' => $contentid, + 'warningcode' => 'nopermissiontomanage', + 'message' => get_string('nopermissiontomanage', 'core_contentbank') + ]; + } + } + } catch (\moodle_exception $e) { + // The content or the context don't exist. + $warnings[] = [ + 'item' => $contentid, + 'warningcode' => 'exception', + 'message' => $e->getMessage() + ]; + } + + return [ + 'result' => $result, + 'warnings' => $warnings + ]; + } + + /** + * rename_content return. + * + * @since Moodle 3.9 + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return new external_single_structure([ + 'result' => new external_value(PARAM_BOOL, 'The processing result'), + 'warnings' => new external_warnings() + ]); + } +} diff --git a/contentbank/templates/renamecontent.mustache b/contentbank/templates/renamecontent.mustache new file mode 100644 index 00000000000..ea638d53207 --- /dev/null +++ b/contentbank/templates/renamecontent.mustache @@ -0,0 +1,30 @@ +{{! + 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 . +}} +{{! + @template core_contentbank/renamecontent + + Example context (json): + { + "contentid": 17, + "name": "Accordion" + } + +}} +
+ + +
diff --git a/contentbank/view.php b/contentbank/view.php index 4c4a4c05213..d34205cda4e 100644 --- a/contentbank/view.php +++ b/contentbank/view.php @@ -35,7 +35,10 @@ $record = $DB->get_record('contentbank_content', ['id' => $id], '*', MUST_EXIST) $context = context::instance_by_id($record->contextid, MUST_EXIST); require_capability('moodle/contentbank:access', $context); -$returnurl = new \moodle_url('/contentbank/index.php'); +$statusmsg = optional_param('statusmsg', '', PARAM_RAW); +$errormsg = optional_param('errormsg', '', PARAM_RAW); + +$returnurl = new \moodle_url('/contentbank/index.php', ['contextid' => $context->id]); $plugin = core_plugin_manager::instance()->get_plugin_info($record->contenttype); if (!$plugin || !$plugin->is_enabled()) { print_error('unsupported', 'core_contentbank', $returnurl); @@ -46,7 +49,6 @@ $title = get_string('contentbank'); if ($PAGE->course) { require_login($PAGE->course->id); } -$returnurl = new \moodle_url('/contentbank/index.php', ['contextid' => $context->id]); $PAGE->set_url(new \moodle_url('/contentbank/view.php', ['id' => $id])); $PAGE->set_context($context); @@ -57,13 +59,32 @@ $PAGE->set_title($title); $PAGE->set_pagetype('contenbank'); $contenttypeclass = "\\$record->contenttype\\contenttype"; -$contenttype = new $contenttypeclass($context); $contentclass = "\\$record->contenttype\\content"; +if (!class_exists($contenttypeclass) || !class_exists($contentclass)) { + print_error('contenttypenotfound', 'error', $returnurl, $record->contenttype); +} +$contenttype = new $contenttypeclass($context); $content = new $contentclass($record); + +// Create the cog menu with all the secondary actions, such as delete, rename... +$actionmenu = new action_menu(); +$actionmenu->set_alignment(action_menu::TR, action_menu::BR); +if ($contenttype->can_manage($content)) { + // Add the rename content item to the menu. + $attributes = [ + 'data-action' => 'renamecontent', + 'data-contentname' => $content->get_name(), + 'data-contentid' => $content->get_id(), + ]; + $actionmenu->add_secondary_action(new action_menu_link( + new moodle_url('#'), + new pix_icon('e/styleparagraph', get_string('rename')), + get_string('rename'), + false, + $attributes + )); +} if ($contenttype->can_delete($content)) { - // Create the cog menu with all the secondary actions, such as delete, rename... - $actionmenu = new action_menu(); - $actionmenu->set_alignment(action_menu::TR, action_menu::BR); // Add the delete content item to the menu. $attributes = [ 'data-action' => 'deletecontent', @@ -78,18 +99,24 @@ if ($contenttype->can_delete($content)) { false, $attributes )); - - // Add the cog menu to the header. - $PAGE->add_header_action(html_writer::div( - $OUTPUT->render($actionmenu), - 'd-print-none', - ['id' => 'region-main-settings-menu'] - )); } +// Add the cog menu to the header. +$PAGE->add_header_action(html_writer::div( + $OUTPUT->render($actionmenu), + 'd-print-none', + ['id' => 'region-main-settings-menu'] +)); + echo $OUTPUT->header(); echo $OUTPUT->box_start('generalbox'); +// If needed, display notifications. +if ($errormsg !== '') { + echo $OUTPUT->notification($errormsg); +} else if ($statusmsg !== '') { + echo $OUTPUT->notification($statusmsg, 'notifysuccess'); +} if ($contenttype->can_access()) { echo $contenttype->get_view_content($record); } diff --git a/lang/en/contentbank.php b/lang/en/contentbank.php index c1c682be81a..1d3b885f488 100644 --- a/lang/en/contentbank.php +++ b/lang/en/contentbank.php @@ -24,23 +24,27 @@ $string['author'] = 'Author'; $string['contentdeleted'] = 'The content has been deleted.'; +$string['contentname'] = 'Content name'; $string['contentnotdeleted'] = 'An error was encountered while trying to delete the content.'; +$string['contentnotrenamed'] = 'An error was encountered while trying to rename the content.'; +$string['contentrenamed'] = 'The content has been renamed.'; $string['deletecontent'] = 'Delete content'; $string['deletecontentconfirm'] = 'Are you sure you want to delete the content \'{$a->name}\' and all associated files? This action cannot be undone.'; $string['file'] = 'Upload content'; $string['file_help'] = 'Files may be stored in the content bank for use in courses. Only files used by content types enabled on the site may be uploaded.'; $string['name'] = 'Content'; $string['nopermissiontodelete'] = 'You do not have permission to delete content.'; +$string['nopermissiontomanage'] = 'You do not have permission to manage content.'; $string['privacy:metadata:content:contenttype'] = 'The contenttype plugin of the content in the content bank.'; $string['privacy:metadata:content:name'] = 'Name of the content in the content bank.'; $string['privacy:metadata:content:timecreated'] = 'The time when the content was created.'; $string['privacy:metadata:content:timemodified'] = 'The time when the content was modified.'; $string['privacy:metadata:content:usercreated'] = 'The user has created the content.'; -$string['privacy:metadata:content:usercreated'] = 'The user who created the content.'; -$string['privacy:metadata:content:usermodified'] = 'Last user has modified the content.'; $string['privacy:metadata:content:usermodified'] = 'The last user who modified the content.'; $string['privacy:metadata:contentbankcontent'] = 'Stores the content of the content bank.'; $string['privacy:metadata:userid'] = 'The ID of the user creating or modifying content bank content.'; +$string['rename'] = 'Rename'; +$string['renamecontent'] = 'Rename content'; $string['timecreated'] = 'Time created'; $string['unsupported'] = 'This content type is not supported.'; $string['upload'] = 'Upload'; diff --git a/lang/en/role.php b/lang/en/role.php index 12a250e00bf..21530dead6c 100644 --- a/lang/en/role.php +++ b/lang/en/role.php @@ -153,7 +153,9 @@ $string['confirmunassignno'] = 'Cancel'; $string['contentbank:access'] = 'Access the content bank'; $string['contentbank:deleteanycontent'] = 'Delete any content from the content bank'; $string['contentbank:deleteowncontent'] = 'Delete content from own content bank'; -$string['contentbank:upload'] = 'Upload content to the content bank'; +$string['contentbank:manageanycontent'] = 'Manage any content from the content bank (rename, move, publish, share, etc.)'; +$string['contentbank:manageowncontent'] = 'Manage content from own content bank (rename, move, publish, share, etc.)'; +$string['contentbank:upload'] = 'Upload new content in the content bank'; $string['context'] = 'Context'; $string['course:activityvisibility'] = 'Hide/show activities'; $string['course:bulkmessaging'] = 'Send a message to many people'; diff --git a/lib/db/access.php b/lib/db/access.php index 155d223427e..93b736ac5b7 100644 --- a/lib/db/access.php +++ b/lib/db/access.php @@ -2522,4 +2522,26 @@ $capabilities = array( 'user' => CAP_ALLOW, ] ], + + // Manage (rename, move, publish, share, etc.) any content from the content bank. + 'moodle/contentbank:manageanycontent' => [ + 'riskbitmask' => RISK_DATALOSS, + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => array( + 'manager' => CAP_ALLOW, + 'coursecreator' => CAP_ALLOW, + ) + ], + + // Manage (rename, move, publish, share, etc.) content created by yourself. + 'moodle/contentbank:manageowncontent' => [ + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => array( + 'manager' => CAP_ALLOW, + 'coursecreator' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + ) + ], ); diff --git a/lib/db/services.php b/lib/db/services.php index a57a95dff56..2c4c81d6dc7 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -2773,6 +2773,15 @@ $functions = array( 'ajax' => 'true', 'capabilities' => 'moodle/contentbank:deleteanycontent', ], + 'core_contentbank_rename_content' => [ + 'classname' => 'core_contentbank\external\rename_content', + 'methodname' => 'execute', + 'classpath' => '', + 'description' => 'Rename a content in the content bank.', + 'type' => 'write', + 'ajax' => 'true', + 'capabilities' => 'moodle/contentbank:manageowncontent', + ], ); $services = array( diff --git a/version.php b/version.php index abaa9c3ed39..4beeb5db493 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2020050200.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2020050200.02; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '3.9dev+ (Build: 20200502)'; // Human-friendly version name From cc58791b6819eaab9556455fd63a50099f54b985 Mon Sep 17 00:00:00 2001 From: Amaia Anabitarte Date: Tue, 21 Apr 2020 18:30:33 +0200 Subject: [PATCH 2/3] MDL-67790 core_contentbank: Behat contentbank content generator --- lib/behat/classes/behat_core_generator.php | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/behat/classes/behat_core_generator.php b/lib/behat/classes/behat_core_generator.php index 253623cf8a4..a1ad3135e17 100644 --- a/lib/behat/classes/behat_core_generator.php +++ b/lib/behat/classes/behat_core_generator.php @@ -217,6 +217,11 @@ class behat_core_generator extends behat_generator_base { 'required' => array('user', 'preference', 'value'), 'switchids' => array('user' => 'userid') ], + 'contentbank content' => [ + 'datagenerator' => 'contentbank_content', + 'required' => array('course', 'contenttype', 'user', 'contentname'), + 'switchids' => array('course' => 'courseid', 'user' => 'userid') + ], ]; } @@ -816,4 +821,22 @@ class behat_core_generator extends behat_generator_base { protected function process_user_preferences(array $data) { set_user_preference($data['preference'], $data['value'], $data['userid']); } + + /** + * Create content in the given context's content bank + * + * @param array $data + * @return void + */ + protected function process_contentbank_content(array $data) { + $context = context_course::instance($data['courseid']); + $contenttypeclass = "\\".$data['contenttype']."\\contenttype"; + if (class_exists($contenttypeclass)) { + $contenttype = new $contenttypeclass($context); + $record = new stdClass(); + $record->usercreated = $data['userid']; + $record->name = $data['contentname']; + $content = $contenttype->create_content($record); + } + } } From 3a6ca392d844d6f652682364e83d716db813598e Mon Sep 17 00:00:00 2001 From: Amaia Anabitarte Date: Fri, 17 Apr 2020 13:40:33 +0200 Subject: [PATCH 3/3] MDL-67790 core_contentbank: Unit and behat tests for renaming content --- .../h5p/tests/behat/manage_content.feature | 47 ++++++ .../h5p/tests/content_h5p_test.php | 4 +- contentbank/tests/content_test.php | 49 ++++++ contentbank/tests/contenttype_test.php | 102 ++++++++++++- .../tests/external/rename_content_test.php | 140 ++++++++++++++++++ 5 files changed, 339 insertions(+), 3 deletions(-) create mode 100644 contentbank/contenttype/h5p/tests/behat/manage_content.feature create mode 100644 contentbank/tests/external/rename_content_test.php diff --git a/contentbank/contenttype/h5p/tests/behat/manage_content.feature b/contentbank/contenttype/h5p/tests/behat/manage_content.feature new file mode 100644 index 00000000000..5705a9e358c --- /dev/null +++ b/contentbank/contenttype/h5p/tests/behat/manage_content.feature @@ -0,0 +1,47 @@ +@core @core_contentbank @contenttype_h5p @_file_upload @javascript +Feature: Manage H5P content from the content bank + In order to manage H5P content in the content bank + As an admin + I need to be able to edit any H5P content in the content bank + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + And the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + And the following "contentbank content" exist: + | course| contenttype | user | contentname | + | C1 | contenttype_h5p | admin | filltheblanks.h5p | + | C1 | contenttype_h5p | teacher1 | ipsums.h5p | + And I log in as "admin" + And I am on "Course 1" course homepage with editing mode on + And I add the "Navigation" block if not present + And I log out + + Scenario: Teachers can rename their own content in the content bank + Given I log in as "teacher1" + And I am on "Course 1" course homepage + And I expand "Site pages" node + And I click on "Content bank" "link" + And I follow "ipsums.h5p" + When I open the action menu in "region-main-settings-menu" "region" + And I should see "Rename" + And I choose "Rename" in the open action menu + And I set the field "Content name" to "New name" + And I click on "Rename" "button" + And I wait until the page is ready + Then I should not see "ipsums.h5p" + And I should see "New name" + + Scenario: Teachers can't rename content created by other users in the content bank + Given I log in as "teacher1" + And I am on "Course 1" course homepage + And I expand "Site pages" node + And I click on "Content bank" "link" + When I follow "filltheblanks.h5p" + Then "region-main-settings-menu" "region" should not exist diff --git a/contentbank/contenttype/h5p/tests/content_h5p_test.php b/contentbank/contenttype/h5p/tests/content_h5p_test.php index a4082f84e07..657a459d768 100644 --- a/contentbank/contenttype/h5p/tests/content_h5p_test.php +++ b/contentbank/contenttype/h5p/tests/content_h5p_test.php @@ -51,14 +51,14 @@ class contenttype_h5p_content_plugin_testcase extends advanced_testcase { // Create a dummy file. $filename = 'content.h5p'; - $dummy = array( + $dummy = [ 'contextid' => \context_system::instance()->id, 'component' => 'contentbank', 'filearea' => 'public', 'itemid' => $content->get_id(), 'filepath' => '/', 'filename' => $filename - ); + ]; $fs = get_file_storage(); $fs->create_file_from_string($dummy, 'dummy content'); diff --git a/contentbank/tests/content_test.php b/contentbank/tests/content_test.php index 04b722f90c9..2fcd66c67b2 100644 --- a/contentbank/tests/content_test.php +++ b/contentbank/tests/content_test.php @@ -64,6 +64,55 @@ class core_contenttype_content_testcase extends \advanced_testcase { $this->assertEquals($record->name, $content->get_name()); } + /** + * Data provider for test_set_name. + * + * @return array + */ + public function set_name_provider() { + return [ + 'Standard name' => ['New name', 'New name'], + 'Name with digits' => ['Today is 17/04/2017', 'Today is 17/04/2017'], + 'Name with symbols' => ['Follow us: @moodle', 'Follow us: @moodle'], + 'Name with tags' => ['This is bold', 'This is bold'], + 'Long name' => [str_repeat('a', 100), str_repeat('a', 100)], + 'Too long name' => [str_repeat('a', 300), str_repeat('a', 255)] + ]; + } + + /** + * Tests for 'set_name' behaviour. + * + * @dataProvider set_name_provider + * @param string $newname The name to set + * @param string $expected The name result + * + * @covers ::set_name + */ + public function test_set_name(string $newname, string $expected) { + global $DB; + + $this->resetAfterTest(); + $this->setAdminUser(); + + $oldname = "Old name"; + $context = context_system::instance(); + + // Create content. + $record = new stdClass(); + $record->name = $oldname; + + $contenttype = new contenttype($context); + $content = $contenttype->create_content($record); + $this->assertEquals($oldname, $content->get_name()); + + $content->set_name($newname); + $this->assertEquals($expected, $content->get_name()); + + $record = $DB->get_record('contentbank_content', ['id' => $content->get_id()]); + $this->assertEquals($expected, $record->name); + } + /** * Tests for behaviour of get_content_type(). * diff --git a/contentbank/tests/contenttype_test.php b/contentbank/tests/contenttype_test.php index 7175b6364a4..e74bd1bd9d2 100644 --- a/contentbank/tests/contenttype_test.php +++ b/contentbank/tests/contenttype_test.php @@ -176,7 +176,6 @@ class core_contenttype_contenttype_testcase extends \advanced_testcase { $this->assertInstanceOf('\\contenttype_testable\\content', $content); } - /** * Test the behaviour of can_delete(). */ @@ -254,4 +253,105 @@ class core_contenttype_contenttype_testcase extends \advanced_testcase { $this->contenttype = new \contenttype_testable\contenttype($systemcontext); } + + /** + * Data provider for test_rename_content. + * + * @return array + */ + public function rename_content_provider() { + return [ + 'Standard name' => ['New name', 'New name'], + 'Name with digits' => ['Today is 17/04/2017', 'Today is 17/04/2017'], + 'Name with symbols' => ['Follow us: @moodle', 'Follow us: @moodle'], + 'Name with tags' => ['This is bold', 'This is bold'], + 'Long name' => [str_repeat('a', 100), str_repeat('a', 100)], + 'Too long name' => [str_repeat('a', 300), str_repeat('a', 255)] + ]; + } + + /** + * Test the behaviour of rename_content(). + * + * @dataProvider rename_content_provider + * @param string $newname The name to set + * @param string $expected The name result + * + * @covers ::rename_content + */ + public function test_rename_content(string $newname, string $expected) { + global $DB; + + $this->resetAfterTest(); + + // Create course and teacher user. + $course = $this->getDataGenerator()->create_course(); + $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); + $coursecontext = \context_course::instance($course->id); + $contenttype = new contenttype($coursecontext); + + // Add some content to the content bank as teacher. + $this->setUser($teacher); + $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); + $contents = $generator->generate_contentbank_data('contenttype_testable', 1, $teacher->id); + $content = array_shift($contents); + + $oldname = $content->get_name(); + + // Check the content is renamed as expected by a user with permission. + $renamed = $contenttype->rename_content($content, $newname); + $this->assertTrue($renamed); + $record = $DB->get_record('contentbank_content', ['id' => $content->get_id()]); + $this->assertNotEquals($oldname, $record->name); + $this->assertEquals($expected, $record->name); + } + + /** + * Test the behaviour of can_manage(). + * + * @covers ::can_manage + */ + public function test_can_manage() { + global $DB, $USER; + + $this->resetAfterTest(); + $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); + + // Create course and teacher user. + $teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher']); + $course = $this->getDataGenerator()->create_course(); + $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); + $manager = $this->getDataGenerator()->create_and_enrol($course, 'manager'); + $coursecontext = \context_course::instance($course->id); + + $contenttype = new contenttype($coursecontext); + + // Add some content to the content bank as admin. + $this->setAdminUser(); + $contentsbyadmin = $generator->generate_contentbank_data('contenttype_testable', 1, $USER->id, $coursecontext); + $contentbyadmin = array_shift($contentsbyadmin); + + // Add some content to the content bank as teacher. + $contentsbyteacher = $generator->generate_contentbank_data('contenttype_testable', 1, $teacher->id, $coursecontext); + $contentbyteacher = array_shift($contentsbyteacher); + + // Check the content has been created as expected. + $records = $DB->count_records('contentbank_content'); + $this->assertEquals(2, $records); + + // Check manager can manage by default all the contents created. + $this->setUser($manager); + $this->assertTrue($contenttype->can_manage($contentbyteacher)); + $this->assertTrue($contenttype->can_manage($contentbyadmin)); + + // Check teacher can only edit their own content. + $this->setUser($teacher); + $this->assertTrue($contenttype->can_manage($contentbyteacher)); + $this->assertFalse($contenttype->can_manage($contentbyadmin)); + + // Unassign capability to teacher role and check they not can not edit any content. + unassign_capability('moodle/contentbank:manageowncontent', $teacherroleid); + $this->assertFalse($contenttype->can_manage($contentbyteacher)); + $this->assertFalse($contenttype->can_manage($contentbyadmin)); + } } diff --git a/contentbank/tests/external/rename_content_test.php b/contentbank/tests/external/rename_content_test.php new file mode 100644 index 00000000000..4b1bf6bb45e --- /dev/null +++ b/contentbank/tests/external/rename_content_test.php @@ -0,0 +1,140 @@ +. + +/** + * Core content bank external functions tests. + * + * @package core_contentbank + * @category external + * @copyright 2020 Sara Arjona + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 3.9 + */ + +namespace core_contentbank; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php'); +require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php'); +require_once($CFG->dirroot . '/webservice/tests/helpers.php'); + +use core_contentbank\external\delete_content; +use core_contentbank\external\external; +use core_contentbank\external\rename_content; +use external_api; + +/** + * Core content bank external functions tests. + * + * @package core_contentbank + * @copyright 2020 Sara Arjona + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @coversDefaultClass \core_contentbank\external + */ +class rename_content_testcase extends \externallib_advanced_testcase { + + /** + * Data provider for test_rename_content. + * + * @return array + */ + public function rename_content_provider() { + return [ + 'Standard name' => ['New name', 'New name'], + 'Name with digits' => ['Today is 17/04/2017', 'Today is 17/04/2017'], + 'Name with symbols' => ['Follow us: @moodle', 'Follow us: @moodle'], + 'Name with tags' => ['This is bold', 'This is bold'], + 'Long name' => [str_repeat('a', 100), str_repeat('a', 100)], + 'Too long name' => [str_repeat('a', 300), str_repeat('a', 255)] + ]; + } + + /** + * Test the behaviour of rename_content() for users with permission. + * + * @dataProvider rename_content_provider + * @param string $newname The name to set + * @param string $expected The name result + * + * @covers ::execute + */ + public function test_rename_content_with_permission(string $newname, string $expected) { + global $DB; + $this->resetAfterTest(); + + // Create users. + $roleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher']); + $teacher = $this->getDataGenerator()->create_user(); + + $this->getDataGenerator()->role_assign($roleid, $teacher->id); + $this->setUser($teacher); + + // Add some content to the content bank as teacher. + $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); + $contents = $generator->generate_contentbank_data('contenttype_testable', 1, $teacher->id); + $content = array_shift($contents); + + $oldname = $content->get_name(); + + // Call the WS and check the content is renamed as expected. + $result = rename_content::execute($content->get_id(), $newname); + $result = external_api::clean_returnvalue(rename_content::execute_returns(), $result); + $this->assertTrue($result['result']); + $record = $DB->get_record('contentbank_content', ['id' => $content->get_id()]); + $this->assertNotEquals($oldname, $record->name); + $this->assertEquals($expected, $record->name); + + // Call the WS using an unexisting contentid and check an error is thrown. + $this->expectException(\invalid_response_exception::class); + $result = rename_content::execute_returns($content->get_id() + 1, $oldname); + $result = external_api::clean_returnvalue(rename_content::execute_returns(), $result); + $this->assertFalse($result['result']); + } + + /** + * Test the behaviour of rename_content() for users with permission. + * + * @covers ::execute + */ + public function test_rename_content_without_permission() { + global $DB; + $this->resetAfterTest(); + + // Create users. + $course = $this->getDataGenerator()->create_course(); + $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); + $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); + + // Add some content to the content bank as teacher. + $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); + $contents = $generator->generate_contentbank_data('contenttype_testable', 1, $teacher->id); + $content = array_shift($contents); + + $oldname = $content->get_name(); + $newname = 'New name'; + + // Call the WS and check the content has not been renamed by the student. + $this->setUser($student); + $result = rename_content::execute($content->get_id(), $newname); + $result = external_api::clean_returnvalue(rename_content::execute_returns(), $result); + $this->assertFalse($result['result']); + $record = $DB->get_record('contentbank_content', ['id' => $content->get_id()]); + $this->assertEquals($oldname, $record->name); + $this->assertNotEquals($newname, $record->name); + } +}