From c0d615e8f911bd943fb87633d01683cd112c4b52 Mon Sep 17 00:00:00 2001 From: Sara Arjona Date: Thu, 16 Apr 2020 12:11:28 +0200 Subject: [PATCH] MDL-67795 contentbank: delete content API --- contentbank/classes/contenttype.php | 53 ++++++++++ .../contenttype/h5p/classes/content.php | 1 + .../contenttype/h5p/classes/contenttype.php | 15 +++ .../h5p/tests/contenttype_h5p_test.php | 41 ++++++++ contentbank/tests/contenttype_test.php | 97 +++++++++++++++++++ lang/en/contentbank.php | 4 + lang/en/role.php | 2 + lib/db/access.php | 22 ++++- 8 files changed, 234 insertions(+), 1 deletion(-) diff --git a/contentbank/classes/contenttype.php b/contentbank/classes/contenttype.php index f4062824821..2b5bc7628ca 100644 --- a/contentbank/classes/contenttype.php +++ b/contentbank/classes/contenttype.php @@ -80,6 +80,25 @@ abstract class contenttype { return null; } + /** + * Delete this content from the content_bank. + * This method can be overwritten by the plugins if they need to delete specific information. + * + * @param content $content The content to delete. + * @return boolean true if the content has been deleted; false otherwise. + */ + public function delete_content(content $content): bool { + global $DB; + + // Delete the file if it exists. + if ($file = $content->get_file()) { + $file->delete(); + } + + // Delete the contentbank DB entry. + return $DB->delete_records('contentbank_content', ['id' => $content->get_id()]); + } + /** * Returns the contenttype name of this content. * @@ -187,6 +206,40 @@ abstract class contenttype { return true; } + /** + * Check if the user can delete this content. + * + * @param content $content The content to be deleted. + * @return bool True if content could be uploaded. False otherwise. + */ + final public function can_delete(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; + } + + $hascapability = has_capability('moodle/contentbank:deleteanycontent', $this->context); + if ($content->get_content()->usercreated == $USER->id) { + // This content has been created by the current user; check if she can delete her content. + $hascapability = $hascapability || has_capability('moodle/contentbank:deleteowncontent', $this->context); + } + + return $hascapability && $this->is_delete_allowed($content); + } + + /** + * Returns if content allows deleting. + * + * @param content $content The content to be deleted. + * @return bool True if content allows uploading. False otherwise. + */ + protected function is_delete_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/contenttype/h5p/classes/content.php b/contentbank/contenttype/h5p/classes/content.php index 1457476af9b..4670423dc98 100644 --- a/contentbank/contenttype/h5p/classes/content.php +++ b/contentbank/contenttype/h5p/classes/content.php @@ -35,4 +35,5 @@ use html_writer; * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class content extends \core_contentbank\content { + } diff --git a/contentbank/contenttype/h5p/classes/contenttype.php b/contentbank/contenttype/h5p/classes/contenttype.php index 6c100cbd437..f28bee8837b 100644 --- a/contentbank/contenttype/h5p/classes/contenttype.php +++ b/contentbank/contenttype/h5p/classes/contenttype.php @@ -36,6 +36,21 @@ use html_writer; */ class contenttype extends \core_contentbank\contenttype { + /** + * Delete this content from the content_bank and remove all the H5P related information. + * + * @param content $content The content to delete. + * @return boolean true if the content has been deleted; false otherwise. + */ + public function delete_content(\core_contentbank\content $content): bool { + // Delete the H5P content. + $factory = new \core_h5p\factory(); + \core_h5p\api::delete_content_from_pluginfile_url($content->get_file_url(), $factory); + + // Delete the content from the content_bank. + return parent::delete_content($content); + } + /** * Returns the HTML content to add to view.php visualizer. * diff --git a/contentbank/contenttype/h5p/tests/contenttype_h5p_test.php b/contentbank/contenttype/h5p/tests/contenttype_h5p_test.php index a2daa82dcaa..3b9e3e3941c 100644 --- a/contentbank/contenttype/h5p/tests/contenttype_h5p_test.php +++ b/contentbank/contenttype/h5p/tests/contenttype_h5p_test.php @@ -34,6 +34,47 @@ */ class contenttype_h5p_contenttype_plugin_testcase extends advanced_testcase { + /** + * Test the behaviour of delete_content(). + */ + public function test_delete_content() { + global $CFG, $USER, $DB; + + $this->resetAfterTest(); + $systemcontext = context_system::instance(); + + // Create users. + $roleid = $DB->get_field('role', 'id', array('shortname' => 'manager')); + $manager = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->role_assign($roleid, $manager->id); + $this->setUser($manager); + + // Add an H5P file to the content bank. + $filepath = $CFG->dirroot . '/h5p/tests/fixtures/filltheblanks.h5p'; + $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); + $contents = $generator->generate_contentbank_data('contenttype_h5p', 2, $USER->id, $systemcontext, true, $filepath); + $content1 = array_shift($contents); + $content2 = array_shift($contents); + + // Load this H5P file though the player to create the H5P DB entries. + $h5pplayer = new \core_h5p\player($content1->get_file_url(), new \stdClass(), true); + $h5pplayer->add_assets_to_page(); + $h5pplayer->output(); + $h5pplayer = new \core_h5p\player($content2->get_file_url(), new \stdClass(), true); + $h5pplayer->add_assets_to_page(); + $h5pplayer->output(); + + // Check the H5P content has been created. + $this->assertEquals(2, $DB->count_records('h5p')); + $this->assertEquals(2, $DB->count_records('contentbank_content')); + + // Check the H5P content is removed after calling this method. + $contenttype = new \contenttype_h5p\contenttype($systemcontext); + $contenttype->delete_content($content1); + $this->assertEquals(1, $DB->count_records('h5p')); + $this->assertEquals(1, $DB->count_records('contentbank_content')); + } + /** * Tests can_upload behavior. * diff --git a/contentbank/tests/contenttype_test.php b/contentbank/tests/contenttype_test.php index ec231a0c5b6..7175b6364a4 100644 --- a/contentbank/tests/contenttype_test.php +++ b/contentbank/tests/contenttype_test.php @@ -46,6 +46,24 @@ use contenttype_testable\contenttype as contenttype; */ class core_contenttype_contenttype_testcase extends \advanced_testcase { + /** @var int Identifier for the manager role. */ + protected $managerroleid; + + /** @var stdClass Manager user. */ + protected $manager1; + + /** @var stdClass Manager user. */ + protected $manager2; + + /** @var stdClass User. */ + protected $user; + + /** @var array List of contents created (every user has a key with contents created by her). */ + protected $contents = []; + + /** @var contenttype The contenttype instance. */ + protected $contenttype; + /** * Tests get_contenttype_name result. * @@ -157,4 +175,83 @@ class core_contenttype_contenttype_testcase extends \advanced_testcase { $this->assertEquals('contenttype_testable', $content->get_content_type()); $this->assertInstanceOf('\\contenttype_testable\\content', $content); } + + + /** + * Test the behaviour of can_delete(). + */ + public function test_can_delete() { + global $DB; + + $this->resetAfterTest(); + $this->contenttype_setup_scenario_data(); + + $managercontent = array_shift($this->contents[$this->manager1->id]); + $usercontent = array_shift($this->contents[$this->user->id]); + + // Check the content has been created as expected. + $records = $DB->count_records('contentbank_content'); + $this->assertEquals(4, $records); + + // Check user can only delete records created by her. + $this->setUser($this->user); + $this->assertFalse($this->contenttype->can_delete($managercontent)); + $this->assertTrue($this->contenttype->can_delete($usercontent)); + + // Check manager can delete records all the records created. + $this->setUser($this->manager1); + $this->assertTrue($this->contenttype->can_delete($managercontent)); + $this->assertTrue($this->contenttype->can_delete($usercontent)); + + // Unassign capability to manager role and check not can only delete their own records. + unassign_capability('moodle/contentbank:deleteanycontent', $this->managerroleid); + $this->assertTrue($this->contenttype->can_delete($managercontent)); + $this->assertFalse($this->contenttype->can_delete($usercontent)); + $this->setUser($this->manager2); + $this->assertFalse($this->contenttype->can_delete($managercontent)); + $this->assertFalse($this->contenttype->can_delete($usercontent)); + } + + /** + * Test the behaviour of delete_content(). + */ + public function test_delete_content() { + global $DB; + + $this->resetAfterTest(); + $this->contenttype_setup_scenario_data(); + + // Check the content has been created as expected. + $this->assertEquals(4, $DB->count_records('contentbank_content')); + + // Check the content is deleted as expected. + $this->setUser($this->manager1); + $content = array_shift($this->contents[$this->manager1->id]); + $deleted = $this->contenttype->delete_content($content); + $this->assertTrue($deleted); + $this->assertEquals(3, $DB->count_records('contentbank_content')); + } + + /** + * Helper function to setup 3 users (manager1, manager2 and user) and 4 contents (3 created by manager1 and 1 by user). + */ + protected function contenttype_setup_scenario_data(): void { + global $DB; + $systemcontext = context_system::instance(); + + // Create users. + $this->manager1 = $this->getDataGenerator()->create_user(); + $this->manager2 = $this->getDataGenerator()->create_user(); + $this->managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager')); + $this->getDataGenerator()->role_assign($this->managerroleid, $this->manager1->id); + $this->getDataGenerator()->role_assign($this->managerroleid, $this->manager2->id); + $this->user = $this->getDataGenerator()->create_user(); + + // Add some content to the content bank. + $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); + $this->contents[$this->manager1->id] = $generator->generate_contentbank_data(null, 3, $this->manager1->id); + $this->contents[$this->user->id] = $generator->generate_contentbank_data(null, 1, $this->user->id); + + $this->contenttype = new \contenttype_testable\contenttype($systemcontext); + } } diff --git a/lang/en/contentbank.php b/lang/en/contentbank.php index dbcb27946f1..29ace781502 100644 --- a/lang/en/contentbank.php +++ b/lang/en/contentbank.php @@ -23,6 +23,10 @@ */ $string['author'] = 'Author'; +$string['contentdeleted'] = 'The content has been deleted.'; +$string['contentnotdeleted'] = 'An error was encountered while trying to delete the content.'; +$string['deletecontent'] = 'Delete content'; +$string['deletecontentconfirm'] = '

Are you sure you want to delete content \'{$a->name}\'? It will remove the content and all its files.

This operation can not 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'; diff --git a/lang/en/role.php b/lang/en/role.php index b6e29047234..a37ebedcb1e 100644 --- a/lang/en/role.php +++ b/lang/en/role.php @@ -151,6 +151,8 @@ $string['confirmunassigntitle'] = 'Confirm role change'; $string['confirmunassignyes'] = 'Remove'; $string['confirmunassignno'] = 'Cancel'; $string['contentbank:access'] = 'Access the content bank'; +$string['contentbank:deleteanycontent'] = 'Delete any content from the content bank on the site'; +$string['contentbank:deleteowncontent'] = 'Delete content from the content bank created by the user'; $string['contentbank:upload'] = 'Upload new content in the content bank'; $string['context'] = 'Context'; $string['course:activityvisibility'] = 'Hide/show activities'; diff --git a/lib/db/access.php b/lib/db/access.php index c74645b6839..155d223427e 100644 --- a/lib/db/access.php +++ b/lib/db/access.php @@ -2501,5 +2501,25 @@ $capabilities = array( 'coursecreator' => CAP_ALLOW, 'editingteacher' => CAP_ALLOW, ) - ) + ), + + // Delete any content from the content bank. + 'moodle/contentbank:deleteanycontent' => [ + 'riskbitmask' => RISK_DATALOSS, + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => [ + 'manager' => CAP_ALLOW, + 'coursecreator' => CAP_ALLOW, + ] + ], + + // Delete content created by yourself. + 'moodle/contentbank:deleteowncontent' => [ + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => [ + 'user' => CAP_ALLOW, + ] + ], );