MDL-67795 contentbank: delete content API
authorSara Arjona <sara@moodle.com>
Thu, 16 Apr 2020 10:11:28 +0000 (12:11 +0200)
committerSara Arjona <sara@moodle.com>
Thu, 23 Apr 2020 11:09:40 +0000 (13:09 +0200)
contentbank/classes/contenttype.php
contentbank/contenttype/h5p/classes/content.php
contentbank/contenttype/h5p/classes/contenttype.php
contentbank/contenttype/h5p/tests/contenttype_h5p_test.php
contentbank/tests/contenttype_test.php
lang/en/contentbank.php
lang/en/role.php
lib/db/access.php

index f406282..2b5bc76 100644 (file)
@@ -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.
      *
index 1457476..4670423 100644 (file)
@@ -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 {
+
 }
index 6c100cb..f28bee8 100644 (file)
@@ -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.
      *
index a2daa82..3b9e3e3 100644 (file)
  */
 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.
      *
index ec231a0..7175b63 100644 (file)
@@ -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);
+    }
 }
index dbcb279..29ace78 100644 (file)
  */
 
 $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'] = '<p>Are you sure you want to delete content <em>\'{$a->name}\'</em>? It will remove the content and all its files.</p><p>This operation can not be undone.</p>';
 $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';
index b6e2904..a37ebed 100644 (file)
@@ -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';
index c74645b..155d223 100644 (file)
@@ -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,
+        ]
+    ],
 );