diff --git a/contentbank/classes/content.php b/contentbank/classes/content.php index 27975cb61f4..13996a9875d 100644 --- a/contentbank/classes/content.php +++ b/contentbank/classes/content.php @@ -100,7 +100,13 @@ abstract class content { } $this->content->usermodified = $USER->id; $this->content->timemodified = time(); - return $DB->update_record('contentbank_content', $this->content); + $result = $DB->update_record('contentbank_content', $this->content); + if ($result) { + // Trigger an event for updating this content. + $event = contentbank_content_updated::create_from_record($this->content); + $event->trigger(); + } + return $result; } /** diff --git a/contentbank/classes/contentbank.php b/contentbank/classes/contentbank.php index 0a21353a974..35499ce6514 100644 --- a/contentbank/classes/contentbank.php +++ b/contentbank/classes/contentbank.php @@ -24,6 +24,8 @@ namespace core_contentbank; +use stored_file; + /** * Content bank class * @@ -202,4 +204,32 @@ class contentbank { return $contents; } + + /** + * Create content from a file information. + * + * @param \context $context Context where to upload the file and content. + * @param int $userid Id of the user uploading the file. + * @param stored_file $file The file to get information from + * @return content + */ + public function create_content_from_file(\context $context, int $userid, stored_file $file): ?content { + global $USER; + if (empty($userid)) { + $userid = $USER->id; + } + // Get the contenttype to manage given file's extension. + $filename = $file->get_filename(); + $extension = $this->get_extension($filename); + $plugin = $this->get_extension_supporter($extension, $context); + $classname = '\\contenttype_'.$plugin.'\\contenttype'; + $record = new \stdClass(); + $record->name = $filename; + $record->usercreated = $userid; + $contentype = new $classname($context); + $content = $contentype->create_content($record); + $event = \core\event\contentbank_content_uploaded::create_from_record($content->get_content()); + $event->trigger(); + return $content; + } } diff --git a/contentbank/classes/contenttype.php b/contentbank/classes/contenttype.php index 05922b7e01c..7f7fbf59694 100644 --- a/contentbank/classes/contenttype.php +++ b/contentbank/classes/contenttype.php @@ -24,6 +24,9 @@ namespace core_contentbank; +use core\event\contentbank_content_created; +use core\event\contentbank_content_deleted; +use core\event\contentbank_content_viewed; use moodle_url; /** @@ -71,10 +74,15 @@ abstract class contenttype { $entry->usermodified = $entry->usercreated; $entry->timemodified = $entry->timecreated; $entry->configdata = $record->configdata ?? ''; + $entry->instanceid = $record->instanceid ?? 0; $entry->id = $DB->insert_record('contentbank_content', $entry); if ($entry->id) { $classname = '\\'.$entry->contenttype.'\\content'; - return new $classname($entry); + $content = new $classname($entry); + // Trigger an event for creating the content. + $event = contentbank_content_created::create_from_record($content->get_content()); + $event->trigger(); + return $content; } return null; } @@ -95,7 +103,23 @@ abstract class contenttype { } // Delete the contentbank DB entry. - return $DB->delete_records('contentbank_content', ['id' => $content->get_id()]); + $result = $DB->delete_records('contentbank_content', ['id' => $content->get_id()]); + if ($result) { + // Trigger an event for deleting this content. + $record = $content->get_content(); + $event = contentbank_content_deleted::create([ + 'objectid' => $content->get_id(), + 'relateduserid' => $record->usercreated, + 'context' => \context::instance_by_id($record->contextid), + 'other' => [ + 'contenttype' => $content->get_content_type(), + 'name' => $content->get_name() + ] + ]); + $event->add_record_snapshot('contentbank_content', $record); + $event->trigger(); + } + return $result; } /** @@ -149,6 +173,10 @@ abstract class contenttype { * @return string HTML code to include in view.php. */ public function get_view_content(\stdClass $record): string { + // Trigger an event for viewing this content. + $event = contentbank_content_viewed::create_from_record($record); + $event->trigger(); + // Main contenttype class can visualize the content, but plugins could overwrite visualization. return ''; } diff --git a/contentbank/contenttype/h5p/classes/contenttype.php b/contentbank/contenttype/h5p/classes/contenttype.php index f28bee8837b..806205d4f63 100644 --- a/contentbank/contenttype/h5p/classes/contenttype.php +++ b/contentbank/contenttype/h5p/classes/contenttype.php @@ -24,6 +24,7 @@ namespace contenttype_h5p; +use core\event\contentbank_content_viewed; use stdClass; use html_writer; @@ -45,7 +46,9 @@ class contenttype extends \core_contentbank\contenttype { 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); + if (!empty($content->get_file_url())) { + \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); @@ -58,6 +61,10 @@ class contenttype extends \core_contentbank\contenttype { * @return string HTML code to include in view.php. */ public function get_view_content(\stdClass $record): string { + // Trigger an event for viewing this content. + $event = contentbank_content_viewed::create_from_record($record); + $event->trigger(); + $content = new content($record); $fileurl = $content->get_file_url(); $html = html_writer::tag('h2', $content->get_name()); diff --git a/contentbank/tests/behat/events.feature b/contentbank/tests/behat/events.feature new file mode 100644 index 00000000000..c52019903e4 --- /dev/null +++ b/contentbank/tests/behat/events.feature @@ -0,0 +1,73 @@ +@core @core_contentbank @_file_upload @_switch_iframe @javascript +Feature: Confirm content bank events are triggered + In order to log content bank actions + As an admin + I need to be able to check triggered events + + Background: + And the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "contentbank content" exist: + | course | contenttype | user | contentname | + | C1 | contenttype_h5p | admin | Existing | + And 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 am on "Course 1" course homepage with editing mode on + And I add the "Navigation" block if not present + + Scenario: Content created and uploaded events when uploading a content file + Given I navigate to "Reports > Live logs" in site administration + And I should not see "Content uploaded" + And I should not see "Content created" + And I am on "Course 1" course homepage + And I expand "Site pages" node + And I click on "Content bank" "link" + When 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" + And I navigate to "Reports > Live logs" in site administration + Then I should see "Content uploaded" + And I should see "Content created" + + Scenario: Content viewed event + Given I navigate to "Reports > Live logs" in site administration + And I should not see "Content viewed" + And I am on "Course 1" course homepage + And I expand "Site pages" node + And I click on "Content bank" "link" + When I click on "Existing" "link" + And I navigate to "Reports > Live logs" in site administration + Then I should see "Content viewed" + + Scenario: Content deleted event + Given I navigate to "Reports > Live logs" in site administration + And I should not see "Content deleted" + And I am on "Course 1" course homepage + And I expand "Site pages" node + And I click on "Content bank" "link" + And I click on "Existing" "link" + And I open the action menu in "region-main-settings-menu" "region" + When I choose "Delete" in the open action menu + And I click on "Delete" "button" in the "Delete content" "dialogue" + And I navigate to "Reports > Live logs" in site administration + Then I should see "Content deleted" + + Scenario: Content updated event when renaming + Given I navigate to "Reports > Live logs" in site administration + And I should not see "Content updated" + And I am on "Course 1" course homepage + And I expand "Site pages" node + And I click on "Content bank" "link" + And I click on "Existing" "link" + And I open the action menu in "region-main-settings-menu" "region" + When 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 navigate to "Reports > Live logs" in site administration + Then I should see "Content updated" \ No newline at end of file diff --git a/contentbank/tests/contentbank_test.php b/contentbank/tests/contentbank_test.php index 824f7a51a6b..e42045643ab 100644 --- a/contentbank/tests/contentbank_test.php +++ b/contentbank/tests/contentbank_test.php @@ -181,11 +181,11 @@ class core_contentbank_testcase extends advanced_testcase { * * @dataProvider search_contents_provider * @param string $search String to search. - * @param int $contextid Contextid to search. + * @param string $where Context where to search. * @param int $expectedresult Expected result. * @param array $contexts List of contexts where to create content. */ - public function test_search_contents(?string $search, int $contextid, int $expectedresult, array $contexts = []): void { + public function test_search_contents(?string $search, string $where, int $expectedresult, array $contexts = []): void { global $DB; $this->resetAfterTest(); @@ -195,11 +195,26 @@ class core_contentbank_testcase extends advanced_testcase { $manager = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->role_assign($managerroleid, $manager->id); + // Create a category and a course. + $coursecat = $this->getDataGenerator()->create_category(); + $course = $this->getDataGenerator()->create_course(); + $existingcontexts = []; + $existingcontexts['system'] = \context_system::instance(); + $existingcontexts['category'] = \context_coursecat::instance($coursecat->id); + $existingcontexts['course'] = \context_course::instance($course->id); + + if (empty($where)) { + $contextid = 0; + } else { + $contextid = $existingcontexts[$where]->id; + } + // Add some content to the content bank. $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); foreach ($contexts as $context) { + $contextinstance = $existingcontexts[$context]; $records = $generator->generate_contentbank_data('contenttype_h5p', 3, - $manager->id, $context, false); + $manager->id, $contextinstance, false); } // Search for some content. @@ -220,98 +235,126 @@ class core_contentbank_testcase extends advanced_testcase { * @return array */ public function search_contents_provider(): array { - // Create a category and a course. - $systemcontext = \context_system::instance(); - $coursecat = $this->getDataGenerator()->create_category(); - $course = $this->getDataGenerator()->create_course(); - $coursecatcontext = \context_coursecat::instance($coursecat->id); - $coursecontext = \context_course::instance($course->id); return [ 'Search all content in all contexts' => [ null, - 0, + '', 9, - [$systemcontext, $coursecatcontext, $coursecontext] + ['system', 'category', 'course'] ], 'Search in all contexts for existing string in all contents' => [ 'content', - 0, + '', 9, - [$systemcontext, $coursecatcontext, $coursecontext] + ['system', 'category', 'course'] ], 'Search in all contexts for unexisting string in all contents' => [ 'chocolate', + '', 0, - 0, - [$systemcontext, $coursecatcontext, $coursecontext] + ['system', 'category', 'course'] ], 'Search in all contexts for existing string in some contents' => [ '1', - 0, + '', 3, - [$systemcontext, $coursecatcontext, $coursecontext] + ['system', 'category', 'course'] ], 'Search in all contexts for existing string in some contents (create only 1 context)' => [ '1', - 0, + '', 1, - [$systemcontext] + ['system'] ], 'Search in system context for existing string in all contents' => [ 'content', - $systemcontext->id, + 'system', 3, - [$systemcontext, $coursecatcontext, $coursecontext] + ['system', 'category', 'course'] ], 'Search in category context for unexisting string in all contents' => [ 'chocolate', - $coursecatcontext->id, + 'category', 0, - [$systemcontext, $coursecatcontext, $coursecontext] + ['system', 'category', 'course'] ], 'Search in course context for existing string in some contents' => [ '1', - $coursecontext->id, + 'course', 1, - [$systemcontext, $coursecatcontext, $coursecontext] + ['system', 'category', 'course'] ], 'Search in system context' => [ null, - $systemcontext->id, + 'system', 3, - [$systemcontext, $coursecatcontext, $coursecontext] + ['system', 'category', 'course'] ], 'Search in course context with existing content' => [ null, - $coursecontext->id, + 'course', 3, - [$systemcontext, $coursecatcontext, $coursecontext] + ['system', 'category', 'course'] ], 'Search in course context without existing content' => [ null, - $coursecontext->id, + 'course', 0, - [$systemcontext, $coursecatcontext] + ['system', 'category'] ], 'Search in an empty contentbank' => [ null, - 0, + '', 0, [] ], 'Search in a context in an empty contentbank' => [ null, - $systemcontext->id, + 'system', 0, [] ], 'Search for a string in an empty contentbank' => [ 'content', - 0, + '', 0, [] ], ]; } + + /** + * Test create_content_from_file function. + * + * @covers ::create_content_from_file + */ + public function test_create_content_from_file() { + global $USER; + + $this->resetAfterTest(); + $this->setAdminUser(); + $systemcontext = \context_system::instance(); + $name = 'dummy_h5p.h5p'; + + // Create a dummy H5P file. + $dummyh5p = array( + 'contextid' => $systemcontext->id, + 'component' => 'contentbank', + 'filearea' => 'public', + 'itemid' => 1, + 'filepath' => '/', + 'filename' => $name, + 'userid' => $USER->id + ); + $fs = get_file_storage(); + $dummyh5pfile = $fs->create_file_from_string($dummyh5p, 'Dummy H5Pcontent'); + + $cb = new \core_contentbank\contentbank(); + $content = $cb->create_content_from_file($systemcontext, $USER->id, $dummyh5pfile); + + $this->assertEquals('contenttype_h5p', $content->get_content_type()); + $this->assertInstanceOf('\\contenttype_h5p\\content', $content); + $this->assertEquals($name, $content->get_name()); + } } diff --git a/contentbank/upload.php b/contentbank/upload.php index 48b651e84b6..0a88a6928ee 100644 --- a/contentbank/upload.php +++ b/contentbank/upload.php @@ -72,25 +72,14 @@ if ($mform->is_cancelled()) { redirect($returnurl); } else if ($formdata = $mform->get_data()) { require_sesskey(); - - // Get the file and the contenttype to manage given file's extension. - $usercontext = context_user::instance($USER->id); + // Get the file and create the content based on it. + $usercontext = \context_user::instance($USER->id); $fs = get_file_storage(); $files = $fs->get_area_files($usercontext->id, 'user', 'draft', $formdata->file, 'itemid, filepath, filename', false); - if (!empty($files)) { $file = reset($files); - $filename = $file->get_filename(); - $extension = $cb->get_extension($filename); - $plugin = $cb->get_extension_supporter($extension, $context); - $classname = '\\contenttype_'.$plugin.'\\contenttype'; - $record = new stdClass(); - $record->name = $filename; - if (class_exists($classname)) { - $contentype = new $classname($context); - $content = $contentype->create_content($record); - file_save_draft_area_files($formdata->file, $contextid, 'contentbank', 'public', $content->get_id()); - } + $content = $cb->create_content_from_file($context, $USER->id, $file); + file_save_draft_area_files($formdata->file, $contextid, 'contentbank', 'public', $content->get_id()); } redirect($returnurl); } diff --git a/lang/en/contentbank.php b/lang/en/contentbank.php index 1d3b885f488..06958d6feca 100644 --- a/lang/en/contentbank.php +++ b/lang/en/contentbank.php @@ -28,6 +28,11 @@ $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['eventcontentcreated'] = 'Content created'; +$string['eventcontentdeleted'] = 'Content deleted'; +$string['eventcontentupdated'] = 'Content updated'; +$string['eventcontentuploaded'] = 'Content uploaded'; +$string['eventcontentviewed'] = 'Content viewed'; $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'; diff --git a/lib/classes/event/contentbank_content_created.php b/lib/classes/event/contentbank_content_created.php new file mode 100644 index 00000000000..bf651c7de02 --- /dev/null +++ b/lib/classes/event/contentbank_content_created.php @@ -0,0 +1,137 @@ +. + +/** + * Contentbank content created event. + * + * @package core + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core\event; + +/** + * Content bank content created class. + * + * @property-read array $other { + * Extra information about event. + * - string contenttype: the contenttype of the content. + * - string name: the name of the content. + * } + * + * @package core + * @since Moodle 3.9 + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class contentbank_content_created extends base { + + /** + * Initialise the event data. + */ + protected function init() { + $this->data['objecttable'] = 'contentbank_content'; + $this->data['crud'] = 'c'; + $this->data['edulevel'] = self::LEVEL_OTHER; + } + + /** + * Creates an event from content bank content object + * + * @since Moodle 3.9 + * @param \stdClass $record Data to create the event + * @return contentbank_content_created + */ + public static function create_from_record(\stdClass $record) { + $event = self::create([ + 'objectid' => $record->id, + 'relateduserid' => $record->usercreated, + 'context' => \context::instance_by_id($record->contextid), + 'other' => [ + 'contenttype' => $record->contenttype, + 'name' => $record->name + ] + ]); + return $event; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventcontentcreated', 'core_contentbank'); + } + + /** + * Returns non-localised description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' created the content with id '$this->objectid'."; + } + + /** + * Custom validation. + * + * @throws \coding_exception + * @return void + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->other['contenttype'])) { + throw new \coding_exception('The \'contenttype\' value must be set in other.'); + } + + if (!isset($this->other['name'])) { + throw new \coding_exception('The \'name\' value must be set in other.'); + } + } + + /** + * Returns relevant URL. + * + * @return \moodle_url + */ + public function get_url() { + $url = new \moodle_url('/contentbank/view.php'); + $url->param('id', $this->objectid); + return $url; + } + + /** + * Used for mapping events on restore + * + * @return array + */ + public static function get_objectid_mapping() { + return array('db' => 'contentbank_content', 'restore' => 'contentbank_content'); + } + + /** + * Used for mapping events on restore + * + * @return bool + */ + public static function get_other_mapping() { + // No mapping required. + return false; + } +} diff --git a/lib/classes/event/contentbank_content_deleted.php b/lib/classes/event/contentbank_content_deleted.php new file mode 100644 index 00000000000..6021d5a178b --- /dev/null +++ b/lib/classes/event/contentbank_content_deleted.php @@ -0,0 +1,126 @@ +. + +/** + * Contentbank content deleted event. + * + * @package core + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core\event; + +/** + * Content bank content deleted class. + * + * @property-read array $other { + * Extra information about event. + * - string contenttype: the contenttype of the content. + * - string name: the name of the content. + * } + * + * @package core + * @since Moodle 3.9 + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class contentbank_content_deleted extends base { + + /** + * Initialise the event data. + */ + protected function init() { + $this->data['objecttable'] = 'contentbank_content'; + $this->data['crud'] = 'd'; + $this->data['edulevel'] = self::LEVEL_OTHER; + } + + /** + * Creates an event from content bank content object + * + * @since Moodle 3.9 + * @param \stdClass $record Data to create the event + * @return contentbank_content_deleted + */ + public static function create_from_record(\stdClass $record) { + $event = self::create([ + 'objectid' => $record->id, + 'relateduserid' => $record->usercreated, + 'context' => \context::instance_by_id($record->contextid), + 'other' => [ + 'contenttype' => $record->contenttype, + 'name' => $record->name + ] + ]); + return $event; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventcontentdeleted', 'core_contentbank'); + } + + /** + * Returns non-localised description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' deleted the content with id '$this->objectid'."; + } + + /** + * Custom validation. + * + * @throws \coding_exception + * @return void + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->other['contenttype'])) { + throw new \coding_exception('The \'contenttype\' value must be set in other.'); + } + + if (!isset($this->other['name'])) { + throw new \coding_exception('The \'name\' value must be set in other.'); + } + } + + /** + * Used for mapping events on restore + * + * @return array + */ + public static function get_objectid_mapping() { + return array('db' => 'contentbank_content', 'restore' => 'contentbank_content'); + } + + /** + * Used for mapping events on restore + * + * @return bool + */ + public static function get_other_mapping() { + // No mapping required. + return false; + } +} diff --git a/lib/classes/event/contentbank_content_updated.php b/lib/classes/event/contentbank_content_updated.php new file mode 100644 index 00000000000..74a3420ead3 --- /dev/null +++ b/lib/classes/event/contentbank_content_updated.php @@ -0,0 +1,137 @@ +. + +/** + * Contentbank content uploaded event. + * + * @package core + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core\event; + +/** + * Content bank content updated class. + * + * @property-read array $other { + * Extra information about event. + * - string contenttype: the contenttype of the content. + * - string name: the name of the content. + * } + * + * @package core + * @since Moodle 3.9 + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class contentbank_content_updated extends base { + + /** + * Initialise the event data. + */ + protected function init() { + $this->data['objecttable'] = 'contentbank_content'; + $this->data['crud'] = 'u'; + $this->data['edulevel'] = self::LEVEL_OTHER; + } + + /** + * Creates an event from content bank content object + * + * @since Moodle 3.9 + * @param \stdClass $record Data to create the event + * @return contentbank_content_updated + */ + public static function create_from_record(\stdClass $record) { + $event = self::create([ + 'objectid' => $record->id, + 'relateduserid' => $record->usercreated, + 'context' => \context::instance_by_id($record->contextid), + 'other' => [ + 'contenttype' => $record->contenttype, + 'name' => $record->name + ] + ]); + return $event; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventcontentupdated', 'core_contentbank'); + } + + /** + * Returns non-localised description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' updated the content with id '$this->objectid'."; + } + + /** + * Custom validation. + * + * @throws \coding_exception + * @return void + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->other['contenttype'])) { + throw new \coding_exception('The \'contenttype\' value must be set in other.'); + } + + if (!isset($this->other['name'])) { + throw new \coding_exception('The \'name\' value must be set in other.'); + } + } + + /** + * Returns relevant URL. + * + * @return \moodle_url + */ + public function get_url() { + $url = new \moodle_url('/contentbank/view.php'); + $url->param('id', $this->objectid); + return $url; + } + + /** + * Used for mapping events on restore + * + * @return array + */ + public static function get_objectid_mapping() { + return array('db' => 'contentbank_content', 'restore' => 'contentbank_content'); + } + + /** + * Used for mapping events on restore + * + * @return bool + */ + public static function get_other_mapping() { + // No mapping required. + return false; + } +} diff --git a/lib/classes/event/contentbank_content_uploaded.php b/lib/classes/event/contentbank_content_uploaded.php new file mode 100644 index 00000000000..1080407e32e --- /dev/null +++ b/lib/classes/event/contentbank_content_uploaded.php @@ -0,0 +1,137 @@ +. + +/** + * Contentbank content uploaded event. + * + * @package core + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core\event; + +/** + * Content bank content uploaded class. + * + * @property-read array $other { + * Extra information about event. + * - string contenttype: the contenttype of the content. + * - string name: the name of the content. + * } + * + * @package core + * @since Moodle 3.9 + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class contentbank_content_uploaded extends base { + + /** + * Initialise the event data. + */ + protected function init() { + $this->data['objecttable'] = 'contentbank_content'; + $this->data['crud'] = 'c'; + $this->data['edulevel'] = self::LEVEL_OTHER; + } + + /** + * Creates an event from content bank content object + * + * @since Moodle 3.9 + * @param \stdClass $record Data to create the event + * @return contentbank_content_uploaded + */ + public static function create_from_record(\stdClass $record) { + $event = self::create([ + 'objectid' => $record->id, + 'relateduserid' => $record->usercreated, + 'context' => \context::instance_by_id($record->contextid), + 'other' => [ + 'contenttype' => $record->contenttype, + 'name' => $record->name + ] + ]); + return $event; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventcontentuploaded', 'core_contentbank'); + } + + /** + * Returns non-localised description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' uploaded the content with id '$this->objectid'."; + } + + /** + * Custom validation. + * + * @throws \coding_exception + * @return void + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->other['contenttype'])) { + throw new \coding_exception('The \'contenttype\' value must be set in other.'); + } + + if (!isset($this->other['name'])) { + throw new \coding_exception('The \'name\' value must be set in other.'); + } + } + + /** + * Returns relevant URL. + * + * @return \moodle_url + */ + public function get_url() { + $url = new \moodle_url('/contentbank/view.php'); + $url->param('id', $this->objectid); + return $url; + } + + /** + * Used for mapping events on restore + * + * @return array + */ + public static function get_objectid_mapping() { + return array('db' => 'contentbank_content', 'restore' => 'contentbank_content'); + } + + /** + * Used for mapping events on restore + * + * @return bool + */ + public static function get_other_mapping() { + // No mapping required. + return false; + } +} diff --git a/lib/classes/event/contentbank_content_viewed.php b/lib/classes/event/contentbank_content_viewed.php new file mode 100644 index 00000000000..cd8b4d2dd4c --- /dev/null +++ b/lib/classes/event/contentbank_content_viewed.php @@ -0,0 +1,137 @@ +. + +/** + * Contentbank content viewed event. + * + * @package core + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core\event; + +/** + * Content bank content updated class. + * + * @property-read array $other { + * Extra information about event. + * - string contenttype: the contenttype of the content. + * - string name: the name of the content. + * } + * + * @package core + * @since Moodle 3.9 + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class contentbank_content_viewed extends base { + + /** + * Initialise the event data. + */ + protected function init() { + $this->data['objecttable'] = 'contentbank_content'; + $this->data['crud'] = 'r'; + $this->data['edulevel'] = self::LEVEL_OTHER; + } + + /** + * Creates an event from content bank content object + * + * @since Moodle 3.9 + * @param \stdClass $record Data to create the event + * @return contentbank_content_viewed + */ + public static function create_from_record(\stdClass $record) { + $event = self::create([ + 'objectid' => $record->id, + 'relateduserid' => $record->usercreated, + 'context' => \context::instance_by_id($record->contextid), + 'other' => [ + 'contenttype' => $record->contenttype, + 'name' => $record->name + ] + ]); + return $event; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventcontentviewed', 'core_contentbank'); + } + + /** + * Returns non-localised description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' viewed the content with id '$this->objectid'."; + } + + /** + * Custom validation. + * + * @throws \coding_exception + * @return void + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->other['contenttype'])) { + throw new \coding_exception('The \'contenttype\' value must be set in other.'); + } + + if (!isset($this->other['name'])) { + throw new \coding_exception('The \'name\' value must be set in other.'); + } + } + + /** + * Returns relevant URL. + * + * @return \moodle_url + */ + public function get_url() { + $url = new \moodle_url('/contentbank/view.php'); + $url->param('id', $this->objectid); + return $url; + } + + /** + * Used for mapping events on restore + * + * @return array + */ + public static function get_objectid_mapping() { + return array('db' => 'contentbank_content', 'restore' => 'contentbank_content'); + } + + /** + * Used for mapping events on restore + * + * @return bool + */ + public static function get_other_mapping() { + // No mapping required. + return false; + } +} diff --git a/lib/tests/event/contentbank_content_created_test.php b/lib/tests/event/contentbank_content_created_test.php new file mode 100644 index 00000000000..0e381b9e6d3 --- /dev/null +++ b/lib/tests/event/contentbank_content_created_test.php @@ -0,0 +1,77 @@ +. + +/** + * Content bank created event tests. + * + * @package core + * @category test + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core\event; + +/** + * Test for content bank created event. + * + * @package core + * @category test + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @coversDefaultClass \core\event\contentbank_content_created + */ +class contentbank_content_created_testcase extends \advanced_testcase { + + /** + * Setup to ensure that fixtures are loaded. + */ + public static function setUpBeforeClass() { + global $CFG; + + require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php'); + require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php'); + } + + /** + * Test the content created event. + * + * @covers ::create_from_record + */ + public function test_content_created() { + + $this->resetAfterTest(); + $this->setAdminUser(); + + // Save the system context. + $systemcontext = \context_system::instance(); + + // Trigger and capture the event when creating a content. + $sink = $this->redirectEvents(); + + // Create a content bank content. + $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); + $contents = $generator->generate_contentbank_data('contenttype_testable', 1); + $content = array_shift($contents); + + $events = $sink->get_events(); + $event = reset($events); + + // Check that the event data is valid. + $this->assertInstanceOf('\core\event\contentbank_content_created', $event); + $this->assertEquals($systemcontext, $event->get_context()); + } +} diff --git a/lib/tests/event/contentbank_content_deleted_test.php b/lib/tests/event/contentbank_content_deleted_test.php new file mode 100644 index 00000000000..20c7517d846 --- /dev/null +++ b/lib/tests/event/contentbank_content_deleted_test.php @@ -0,0 +1,83 @@ +. + +/** + * Content bank deleted event tests. + * + * @package core + * @category test + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core\event; + +/** + * Test for content bank deleted event. + * + * @package core + * @category test + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @coversDefaultClass \core\event\contentbank_content_deleted + */ +class contentbank_content_deleted_testcase extends \advanced_testcase { + + /** + * Setup to ensure that fixtures are loaded. + */ + public static function setUpBeforeClass() { + global $CFG; + + require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php'); + require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php'); + } + + /** + * Test the content deleted event + * + * @covers ::create_from_record + */ + public function test_content_deleted() { + global $DB; + + $this->resetAfterTest(); + $this->setAdminUser(); + + // Save the system context. + $systemcontext = \context_system::instance(); + + // Create a content bank content. + $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); + $contents = $generator->generate_contentbank_data('contenttype_testable', 3); + $content = array_shift($contents); + $this->assertEquals(3, $DB->count_records('contentbank_content')); + + $classname = '\\contenttype_testable\\contenttype'; + $contentype = new $classname($systemcontext); + + // Trigger and capture the event for deleting a content. + $sink = $this->redirectEvents(); + $contentype->delete_content($content); + $events = $sink->get_events(); + $event = reset($events); + + // Check that the content was deleted and the event data is valid. + $this->assertEquals(2, $DB->count_records('contentbank_content')); + $this->assertInstanceOf('\core\event\contentbank_content_deleted', $event); + $this->assertEquals(\context_system::instance(), $event->get_context()); + } +} diff --git a/lib/tests/event/contentbank_content_updated_test.php b/lib/tests/event/contentbank_content_updated_test.php new file mode 100644 index 00000000000..b6aeae790d3 --- /dev/null +++ b/lib/tests/event/contentbank_content_updated_test.php @@ -0,0 +1,82 @@ +. + +/** + * Content bank updated event tests. + * + * @package core + * @category test + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core\event; + +/** + * Test for content bank updated event. + * + * @package core + * @category test + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @coversDefaultClass \core\event\contentbank_content_updated + */ +class contentbank_content_updated_testcase extends \advanced_testcase { + + /** + * Setup to ensure that fixtures are loaded. + */ + public static function setUpBeforeClass() { + global $CFG; + + require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php'); + require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php'); + } + + /** + * Test the content updated event. + * + * @covers ::create_from_record + */ + public function test_content_updated() { + + $this->resetAfterTest(); + $this->setAdminUser(); + + // Save the system context. + $systemcontext = \context_system::instance(); + + // Create a content bank content. + $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); + $contents = $generator->generate_contentbank_data('contenttype_testable', 1); + $content = array_shift($contents); + + // Store the name before we change it. + $oldname = $content->get_name(); + + // Trigger and capture the event when renaming a content. + $sink = $this->redirectEvents(); + + $newname = "New name"; + $content->set_name($newname); + $events = $sink->get_events(); + $event = reset($events); + + // Check that the event data is valid. + $this->assertInstanceOf('\core\event\contentbank_content_updated', $event); + $this->assertEquals($systemcontext, $event->get_context()); + } +} diff --git a/lib/tests/event/contentbank_content_uploaded_test.php b/lib/tests/event/contentbank_content_uploaded_test.php new file mode 100644 index 00000000000..93e6d7d045d --- /dev/null +++ b/lib/tests/event/contentbank_content_uploaded_test.php @@ -0,0 +1,94 @@ +. + +/** + * Content bank uploaded event tests. + * + * @package core + * @category test + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core\event; + +use core_contentbank\contentbank; + +/** + * Test for content bank uploaded event. + * + * @package core + * @category test + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @coversDefaultClass \core\event\contentbank_content_uploaded + */ +class contentbank_content_uploaded_testcase extends \advanced_testcase { + + /** + * Setup to ensure that fixtures are loaded. + */ + public static function setUpBeforeClass() { + global $CFG; + + require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php'); + require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php'); + } + + /** + * Test the content created event. + * + * @covers ::create_from_record + */ + public function test_content_created() { + global $USER; + + $this->resetAfterTest(); + $this->setAdminUser(); + $systemcontext = \context_system::instance(); + + // Create a dummy H5P file. + $dummyh5p = array( + 'contextid' => $systemcontext->id, + 'component' => 'contentbank', + 'filearea' => 'public', + 'itemid' => 1, + 'filepath' => '/', + 'filename' => 'dummy_h5p.h5p' + ); + $fs = get_file_storage(); + $dummyh5pfile = $fs->create_file_from_string($dummyh5p, 'Dummy H5Pcontent'); + + // Trigger and capture the event when creating content from a file. + $sink = $this->redirectEvents(); + $cb = new contentbank(); + $cb->create_content_from_file($systemcontext, $USER->id, $dummyh5pfile); + + // Both uploaded and created events are raised. + $events = $sink->get_events(); + $this->assertCount(2, $events); + + // First the created content event has been raised. + $event = array_shift($events); + $this->assertInstanceOf('\core\event\contentbank_content_created', $event); + $this->assertEquals($systemcontext, $event->get_context()); + + // Second the uploaded content event has been raised. + $event = array_pop($events); + $this->assertInstanceOf('\core\event\contentbank_content_uploaded', $event); + $this->assertEquals($systemcontext, $event->get_context()); + } +} diff --git a/lib/tests/event/contentbank_content_viewed_test.php b/lib/tests/event/contentbank_content_viewed_test.php new file mode 100644 index 00000000000..acc7e53d521 --- /dev/null +++ b/lib/tests/event/contentbank_content_viewed_test.php @@ -0,0 +1,79 @@ +. + +/** + * Content bank viewed event tests. + * + * @package core + * @category test + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core\event; + +/** + * Test for content bank viewed event. + * + * @package core + * @category test + * @copyright 2020 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @coversDefaultClass \core\event\contentbank_content_viewed + */ +class contentbank_content_viewed_testcase extends \advanced_testcase { + + /** + * Setup to ensure that fixtures are loaded. + */ + public static function setUpBeforeClass() { + global $CFG; + + require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php'); + require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php'); + } + + /** + * Test the content updated event. + * + * @covers ::create_from_record + */ + public function test_content_updated() { + + $this->resetAfterTest(); + $this->setAdminUser(); + + // Save the system context. + $systemcontext = \context_system::instance(); + + // Create a content bank content. + $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); + $contents = $generator->generate_contentbank_data('contenttype_testable', 1); + $content = array_shift($contents); + + // Trigger and capture the content viewed event. + $sink = $this->redirectEvents(); + $eventtotrigger = \core\event\contentbank_content_viewed::create_from_record($content->get_content()); + $eventtotrigger->trigger(); + + $events = $sink->get_events(); + $event = reset($events); + + // Check that the event data is valid. + $this->assertInstanceOf('\core\event\contentbank_content_viewed', $event); + $this->assertEquals($systemcontext, $event->get_context()); + } +}