diff --git a/course/lib.php b/course/lib.php index 868585985dc..2b7d15233f3 100644 --- a/course/lib.php +++ b/course/lib.php @@ -4832,8 +4832,32 @@ function course_output_fragment_course_overview($args) { } $modname = $args['modname']; $course = get_course($args['courseid']); + $context = context_course::instance($course->id, MUST_EXIST); can_access_course($course); + // Some plugins may have a list view event. + $eventclassname = 'mod_' . $modname . '\\event\\course_module_instance_list_viewed'; + // Do not confuse this "resource" with the "mod_resource" module. + // This "resource" is the table that aggregate all activities considered "resources" + // (files, folders, pages, text and media...). While the "mod_resource" is a poorly + // named plugin representing an uploaded file, and it is also one of the activities + // that can be aggregated in the "resource" table. + if ($modname === 'resource') { + $eventclassname = 'core\\event\\course_resources_list_viewed'; + } + if (class_exists($eventclassname)) { + try { + $event = $eventclassname::create(['context' => $context]); + $event->add_record_snapshot('course', $course); + $event->trigger(); + } catch (\Throwable $th) { + // This may happens if the plugin implements a custom event class. + // It is highly unlikely but we should not stop the rendering because of this. + // Instead, we will log the error and continue. + debugging('Error while triggering the course module instance viewed event: ' . $th->getMessage()); + } + } + $content = ''; $format = course_get_format($course); $renderer = $format->get_renderer($PAGE); diff --git a/course/overview.php b/course/overview.php index 93d88c5c896..89605bd0eab 100644 --- a/course/overview.php +++ b/course/overview.php @@ -40,6 +40,11 @@ $context = context_course::instance($course->id, MUST_EXIST); require_login($course); require_capability('moodle/course:viewoverview', $context); +// Trigger event, course information viewed. +$event = \core\event\course_overview_viewed::create(['context' => $context]); +$event->add_record_snapshot('course', $course); +$event->trigger(); + $format = course_get_format($course); $renderer = $format->get_renderer($PAGE); $overviewpageclass = $format->get_output_classname('overview\\overviewpage'); diff --git a/course/tests/behat/course_overview.feature b/course/tests/behat/course_overview.feature index 6c62d82c900..b48e1ba3c3d 100644 --- a/course/tests/behat/course_overview.feature +++ b/course/tests/behat/course_overview.feature @@ -215,3 +215,38 @@ Feature: Users can access the course activities overview page When I am on the "Course 1" "course > activities > resource" page logged in as "student1" Then I should see "To do" in the "Activity 1" "table_row" And I should see "View" in the "Activity 1" "table_row" + + Scenario: The course overview page should log a page event and a reource list event + Given the following "activity" exists: + | activity | folder | + | name | Activity 1 | + | course | C1 | + And I am on the "Course 1" "course > activities" page logged in as "teacher1" + And I am on the "Course 1" "course > activities > resource" page logged in as "student1" + When I am on the "Course 1" "course" page logged in as "teacher1" + And I navigate to "Reports" in current page administration + And I click on "Logs" "link" + Then I set the field "Select a user" to "Teacher 1" + And I click on "Get these logs" "button" + And I should see "Course activities overview page viewed" + And I should not see "viewed the list of resources" + And I set the field "Select a user" to "Student 1" + And I click on "Get these logs" "button" + And I should see "Course activities overview page viewed" + And I should see "viewed the list of resources" + + @javascript + Scenario: The course overview page should log reource list event when loading the overview table + Given the following "activity" exists: + | activity | folder | + | name | Activity 1 | + | course | C1 | + And I am on the "Course 1" "course > activities" page logged in as "teacher1" + And I click on "Expand" "link" in the "resource_overview_collapsible" "region" + When I am on the "Course 1" "course" page logged in as "teacher1" + And I navigate to "Reports" in current page administration + And I click on "Logs" "link" + And I set the field "Select a user" to "Teacher 1" + And I click on "Get these logs" "button" + Then I should see "Course activities overview page viewed" + And I should see "viewed the list of resources" diff --git a/course/tests/event/events_test.php b/course/tests/event/events_test.php index ad4812d069f..4b6eb9216ac 100644 --- a/course/tests/event/events_test.php +++ b/course/tests/event/events_test.php @@ -137,4 +137,37 @@ final class events_test extends \advanced_testcase { $sink->close(); } + + /** + * Test the course activities overview page viewed. + * + * There is no external API for viewing course information so the unit test will simply + * create and trigger the event and ensure data is returned as expected. + * + * @covers \core\event\course_overview_viewed + */ + public function test_course_overview_viewed_event(): void { + + // Create a course. + $data = new \stdClass(); + $course = $this->getDataGenerator()->create_course($data); + + $eventparams = [ + 'context' => \context_course::instance($course->id), + ]; + $event = \core\event\course_overview_viewed::create($eventparams); + + // Trigger and capture the event. + $sink = $this->redirectEvents(); + $event->trigger(); + $events = $sink->get_events(); + $event = reset($events); + + // Check that the event data is valid. + $this->assertInstanceOf('\core\event\course_overview_viewed', $event); + $this->assertEquals($course->id, $event->courseid); + $this->assertDebuggingNotCalled(); + $sink->close(); + + } } diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 7ee066f32b4..349b2cb6754 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -834,6 +834,7 @@ $string['eventcoursemodulecreated'] = 'Course module created'; $string['eventcoursemoduledeleted'] = 'Course module deleted'; $string['eventcoursemoduleupdated'] = 'Course module updated'; $string['eventcoursemoduleviewed'] = 'Course module viewed'; +$string['eventcourseoverviewviewed'] = 'Course activities overview page viewed'; $string['eventcoursessearched'] = 'Courses searched'; $string['eventcourseresetended'] = 'Course reset ended'; $string['eventcourseresetstarted'] = 'Course reset started'; diff --git a/lib/classes/event/course_overview_viewed.php b/lib/classes/event/course_overview_viewed.php new file mode 100644 index 00000000000..9a9152ed798 --- /dev/null +++ b/lib/classes/event/course_overview_viewed.php @@ -0,0 +1,77 @@ +. + +namespace core\event; + +use core\url; + +/** + * Event course_overview_viewed + * + * @package core + * @copyright 2025 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class course_overview_viewed extends \core\event\base { + + /** + * Set basic properties for the event. + */ + protected function init() { + $this->data['crud'] = 'r'; + $this->data['edulevel'] = self::LEVEL_PARTICIPATING; + } + + /** + * Returns description of what happened. + * + * @return string + */ + public function get_description(): string { + return "The user with id '$this->userid' viewed the course activity overview for the course with id '$this->courseid'."; + } + + /** + * Return localised event name. + * + * @return string + */ + public static function get_name(): string { + return get_string('eventcourseoverviewviewed', 'core'); + } + + /** + * Get URL related to the action. + * + * @return url|null + */ + public function get_url() { + return new url('/course/overview.php', ['id' => $this->courseid]); + } + + /** + * Custom validation. + * + * @throws \coding_exception + * @return void + */ + protected function validate_data() { + parent::validate_data(); + if ($this->contextlevel != CONTEXT_COURSE) { + throw new \coding_exception('Context level must be CONTEXT_COURSE.'); + } + } +}