diff --git a/contentbank/amd/build/actions.min.js b/contentbank/amd/build/actions.min.js
new file mode 100644
index 00000000000..2e95d9ad9e3
--- /dev/null
+++ b/contentbank/amd/build/actions.min.js
@@ -0,0 +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}}});
+//# sourceMappingURL=actions.min.js.map
diff --git a/contentbank/amd/build/actions.min.js.map b/contentbank/amd/build/actions.min.js.map
new file mode 100644
index 00000000000..05f46fb879d
--- /dev/null
+++ b/contentbank/amd/build/actions.min.js.map
@@ -0,0 +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
diff --git a/contentbank/amd/src/actions.js b/contentbank/amd/src/actions.js
new file mode 100644
index 00000000000..caa25c53780
--- /dev/null
+++ b/contentbank/amd/src/actions.js
@@ -0,0 +1,162 @@
+// 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 manage content bank actions, such as delete or rename.
+ *
+ * @module core_contentbank/actions
+ * @package core_contentbank
+ * @copyright 2020 Sara Arjona
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define([
+ 'jquery',
+ 'core/ajax',
+ 'core/notification',
+ 'core/str',
+ 'core/templates',
+ 'core/url',
+ 'core/modal_factory',
+ 'core/modal_events'],
+function($, Ajax, Notification, Str, Templates, Url, ModalFactory, ModalEvents) {
+
+ /**
+ * List of action selectors.
+ *
+ * @type {{DELETE_CONTENT: string}}
+ */
+ var ACTIONS = {
+ DELETE_CONTENT: '[data-action="deletecontent"]',
+ };
+
+ /**
+ * Actions class.
+ */
+ var Actions = function() {
+ this.registerEvents();
+ };
+
+ /**
+ * Register event listeners.
+ */
+ Actions.prototype.registerEvents = function() {
+ $(ACTIONS.DELETE_CONTENT).click(function(e) {
+ e.preventDefault();
+
+ var contentname = $(this).data('contentname');
+ var contentid = $(this).data('contentid');
+ var contextid = $(this).data('contextid');
+
+ var strings = [
+ {
+ key: 'deletecontent',
+ component: 'core_contentbank'
+ },
+ {
+ key: 'deletecontentconfirm',
+ component: 'core_contentbank',
+ param: {
+ name: contentname,
+ }
+ },
+ {
+ key: 'delete',
+ component: 'core'
+ },
+ ];
+
+ var deleteButtonText = '';
+ Str.get_strings(strings).then(function(langStrings) {
+ var modalTitle = langStrings[0];
+ var modalContent = langStrings[1];
+ deleteButtonText = langStrings[2];
+
+ return ModalFactory.create({
+ title: modalTitle,
+ body: modalContent,
+ type: ModalFactory.types.SAVE_CANCEL,
+ large: true
+ });
+ }).done(function(modal) {
+ modal.setSaveButtonText(deleteButtonText);
+ modal.getRoot().on(ModalEvents.save, function() {
+ // The action is now confirmed, sending an action for it.
+ return deleteContent(contentid, contextid);
+ });
+
+ // Handle hidden event.
+ modal.getRoot().on(ModalEvents.hidden, function() {
+ // Destroy when hidden.
+ modal.destroy();
+ });
+
+ // Show the modal.
+ modal.show();
+
+ return;
+ }).catch(Notification.exception);
+ });
+ };
+
+ /**
+ * Delete content from the content bank.
+ *
+ * @param {int} contentid The content to delete.
+ * @param {int} contextid The contextid where the content belongs.
+ */
+ function deleteContent(contentid, contextid) {
+ var request = {
+ methodname: 'core_contentbank_delete_content',
+ args: {
+ contentids: {contentid}
+ }
+ };
+
+ var requestType = 'success';
+ Ajax.call([request])[0].then(function(data) {
+ if (data.result) {
+ return Str.get_string('contentdeleted', 'core_contentbank');
+ }
+ requestType = 'error';
+ return Str.get_string('contentnotdeleted', 'core_contentbank');
+
+ }).done(function(message) {
+ var params = {
+ contextid: contextid
+ };
+ if (requestType == 'success') {
+ params.statusmsg = message;
+ } else {
+ params.errormsg = message;
+ }
+ // Redirect to the main content bank page and display the message as a notification.
+ window.location.href = Url.relativeUrl('contentbank/index.php', params, false);
+ }).fail(Notification.exception);
+ }
+
+ return /** @alias module:core_contentbank/actions */ {
+ // Public variables and functions.
+
+ /**
+ * Initialise the contentbank actions.
+ *
+ * @method init
+ * @return {Actions}
+ */
+ 'init': function() {
+ return new Actions();
+ }
+ };
+});
diff --git a/contentbank/index.php b/contentbank/index.php
index 01d1db676b1..3a3e9c31a00 100644
--- a/contentbank/index.php
+++ b/contentbank/index.php
@@ -31,6 +31,9 @@ $context = context::instance_by_id($contextid, MUST_EXIST);
require_capability('moodle/contentbank:access', $context);
+$statusmsg = optional_param('statusmsg', '', PARAM_RAW);
+$errormsg = optional_param('errormsg', '', PARAM_RAW);
+
$title = get_string('contentbank');
\core_contentbank\helper::get_page_ready($context, $title);
if ($PAGE->course) {
@@ -74,6 +77,14 @@ if (has_capability('moodle/contentbank:upload', $context)) {
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');
+}
+
+// Render the contentbank contents.
$folder = new \core_contentbank\output\bankcontent($foldercontents, $toolbar, $context);
echo $OUTPUT->render($folder);
diff --git a/contentbank/tests/behat/delete_content.feature b/contentbank/tests/behat/delete_content.feature
new file mode 100644
index 00000000000..4b370b11aeb
--- /dev/null
+++ b/contentbank/tests/behat/delete_content.feature
@@ -0,0 +1,70 @@
+@core @core_contentbank @contentbank_h5p @_file_upload @javascript
+Feature: Delete H5P file from the content bank
+ In order remove H5P content from the content bank
+ As an admin
+ I need to be able to delete any H5P content from the content bank
+
+ Background:
+ Given I log in as "admin"
+ And I follow "Manage private files..."
+ And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
+ And I click on "Save changes" "button"
+ And I click on "Content bank" "link"
+ And I click on "Upload" "link"
+ And I click on "Choose a file..." "button"
+ And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+ And I click on "filltheblanks.h5p" "link"
+ And I click on "Select this file" "button"
+ And I click on "Save changes" "button"
+
+ Scenario: Admins can delete content from the content bank
+ Given I click on "Content bank" "link"
+ And I wait until the page is ready
+ And I should see "filltheblanks.h5p"
+ When I follow "filltheblanks.h5p"
+ And I open the action menu in "region-main-settings-menu" "region"
+ Then I should see "Delete"
+ And I choose "Delete" in the open action menu
+ And I should see "Are you sure you want to delete content 'filltheblanks.h5p'?"
+ And I click on "Cancel" "button" in the "Delete content" "dialogue"
+ And I should see "filltheblanks.h5p"
+ And I open the action menu in "region-main-settings-menu" "region"
+ And I choose "Delete" in the open action menu
+ And I click on "Delete" "button" in the "Delete content" "dialogue"
+ And I wait until the page is ready
+ And I should see "The content has been deleted."
+ And I should not see "filltheblanks.h5p"
+
+ Scenario: Users without the required capability can only delete their own content
+ Given the following "permission overrides" exist:
+ | capability | permission | role | contextlevel | reference |
+ | moodle/contentbank:deleteanycontent | Prohibit | manager | System | |
+ And the following "users" exist:
+ | username | firstname | lastname | email |
+ | manager | Max | Manager | man@example.com |
+ And the following "role assigns" exist:
+ | user | role | contextlevel | reference |
+ | manager | manager | System | |
+ And I log out
+ When I log in as "manager"
+ And I click on "Content bank" "link"
+ And I wait until the page is ready
+ And I should see "filltheblanks.h5p"
+ And I follow "filltheblanks.h5p"
+ Then ".header-actions-container" "css_element" should not exist
+ And I click on "Private files" "link"
+ And I upload "h5p/tests/fixtures/find-the-words.h5p" file to "Files" filemanager
+ And I click on "Save changes" "button"
+ And I click on "Content bank" "link"
+ And I click on "Upload" "link"
+ And I click on "Choose a file..." "button"
+ And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+ And I click on "find-the-words.h5p" "link"
+ And I click on "Select this file" "button"
+ And I click on "Save changes" "button"
+ And I wait until the page is ready
+ And I should see "filltheblanks.h5p"
+ And I should see "find-the-words.h5p"
+ When I follow "find-the-words.h5p"
+ And I open the action menu in "region-main-settings-menu" "region"
+ Then I should see "Delete"
diff --git a/contentbank/view.php b/contentbank/view.php
index 9514bdf62f9..4c4a4c05213 100644
--- a/contentbank/view.php
+++ b/contentbank/view.php
@@ -27,6 +27,10 @@ require('../config.php');
require_login();
$id = required_param('id', PARAM_INT);
+$deletecontent = optional_param('deletecontent', null, PARAM_INT);
+
+$PAGE->requires->js_call_amd('core_contentbank/actions', 'init');
+
$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);
@@ -52,15 +56,42 @@ $title .= ": ".$record->name;
$PAGE->set_title($title);
$PAGE->set_pagetype('contenbank');
+$contenttypeclass = "\\$record->contenttype\\contenttype";
+$contenttype = new $contenttypeclass($context);
+$contentclass = "\\$record->contenttype\\content";
+$content = new $contentclass($record);
+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',
+ 'data-contentname' => $content->get_name(),
+ 'data-contentid' => $content->get_id(),
+ 'data-contextid' => $context->id,
+ ];
+ $actionmenu->add_secondary_action(new action_menu_link(
+ new moodle_url('#'),
+ new pix_icon('t/delete', get_string('delete')),
+ get_string('delete'),
+ 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']
+ ));
+}
+
echo $OUTPUT->header();
echo $OUTPUT->box_start('generalbox');
-$managerlass = "\\$record->contenttype\\contenttype";
-if (class_exists($managerlass)) {
- $manager = new $managerlass($context);
- if ($manager->can_access()) {
- echo $manager->get_view_content($record);
- }
+if ($contenttype->can_access()) {
+ echo $contenttype->get_view_content($record);
}
echo $OUTPUT->box_end();