From 76dbebef1a6a89c796e765d0bdbecb8c304b2949 Mon Sep 17 00:00:00 2001 From: sam marshall Date: Thu, 14 Aug 2025 14:58:39 +0100 Subject: [PATCH] MDL-86308 assignfeedback_file: Log download of feedback files --- .../classes/event/feedback_downloaded.php | 79 ++++++++++++++++ .../file/lang/en/assignfeedback_file.php | 1 + public/mod/assign/feedback/file/lib.php | 4 + .../tests/event/feedback_downloaded_test.php | 94 +++++++++++++++++++ 4 files changed, 178 insertions(+) create mode 100644 public/mod/assign/feedback/file/classes/event/feedback_downloaded.php create mode 100644 public/mod/assign/feedback/file/tests/event/feedback_downloaded_test.php diff --git a/public/mod/assign/feedback/file/classes/event/feedback_downloaded.php b/public/mod/assign/feedback/file/classes/event/feedback_downloaded.php new file mode 100644 index 00000000000..cfee6fdfd6c --- /dev/null +++ b/public/mod/assign/feedback/file/classes/event/feedback_downloaded.php @@ -0,0 +1,79 @@ +. + +namespace assignfeedback_file\event; + +/** + * One or all of the feedback files have been downloaded. + * + * @package assignfeedback_file + * @since Moodle 5.1 + * @copyright 2025 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class feedback_downloaded extends \core\event\base { + #[\Override] + public function get_description() { + return "The user with id '$this->userid' downloaded feeedback file '{$this->other['filename']}' (" . + "'{$this->other['fileid']}') for the assignment with course module id '$this->contextinstanceid'."; + } + + #[\Override] + public static function get_name() { + return get_string('eventfeedback_downloaded', 'assignfeedback_file'); + } + + #[\Override] + protected function init() { + $this->data['crud'] = 'r'; + $this->data['edulevel'] = self::LEVEL_PARTICIPATING; + $this->data['objecttable'] = 'assign_grades'; + } + + /** + * Creates the event. + * + * @param \stored_file $file File that was downloaded + * @return feedback_downloaded Event instance + */ + public static function create_for_file(\stored_file $file): feedback_downloaded { + return self::create([ + 'contextid' => $file->get_contextid(), + 'objectid' => $file->get_itemid(), // The file item id is the assign_grades id. + 'other' => [ + 'fileid' => $file->get_id(), + 'filename' => $file->get_filename(), + ], + ]); + } + + #[\Override] + protected function validate_data() { + if (empty($this->other['fileid'])) { + throw new \coding_exception('other[\'fileid\'] must be set'); + } + if (empty($this->other['filename'])) { + throw new \coding_exception('other[\'filename\'] must be set'); + } + + parent::validate_data(); + } + + #[\Override] + public static function get_objectid_mapping() { + return ['db' => 'assign_grades', 'restore' => 'grade']; + } +} diff --git a/public/mod/assign/feedback/file/lang/en/assignfeedback_file.php b/public/mod/assign/feedback/file/lang/en/assignfeedback_file.php index 0659095e4b8..60b85f627b9 100644 --- a/public/mod/assign/feedback/file/lang/en/assignfeedback_file.php +++ b/public/mod/assign/feedback/file/lang/en/assignfeedback_file.php @@ -33,6 +33,7 @@ $string['default'] = 'Enabled by default'; $string['default_help'] = 'If set, this feedback method will be enabled by default for all new assignments.'; $string['enabled'] = 'File feedback'; $string['enabled_help'] = 'If enabled, the teacher will be able to upload files with feedback when marking assignment submissions. These files may be, but are not limited to, marked-up student submissions, documents with comments or spoken audio feedback.'; +$string['eventfeedback_downloaded'] = 'Feedback file downloaded'; $string['feedbackzip'] = 'Zip file with feedback files'; $string['feedbackfileadded'] = 'New feedback file "{$a->filename}" for student "{$a->student}"'; $string['feedbackfileupdated'] = 'Modified feedback file "{$a->filename}" for student "{$a->student}"'; diff --git a/public/mod/assign/feedback/file/lib.php b/public/mod/assign/feedback/file/lib.php index 1a33d43546a..bdc910660e2 100644 --- a/public/mod/assign/feedback/file/lib.php +++ b/public/mod/assign/feedback/file/lib.php @@ -74,6 +74,10 @@ function assignfeedback_file_pluginfile($course, if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { return false; } + + // Log event so that we can later track if students downloaded their feedback files. + \assignfeedback_file\event\feedback_downloaded::create_for_file($file)->trigger(); + // Download MUST be forced - security! send_stored_file($file, 0, 0, true, $options); } diff --git a/public/mod/assign/feedback/file/tests/event/feedback_downloaded_test.php b/public/mod/assign/feedback/file/tests/event/feedback_downloaded_test.php new file mode 100644 index 00000000000..c0604d6fccb --- /dev/null +++ b/public/mod/assign/feedback/file/tests/event/feedback_downloaded_test.php @@ -0,0 +1,94 @@ +. + +namespace assignfeedback_file\event; + +/** + * Tests {@see feedback_downloaded} event. + * + * @package assignfeedback_file + * @category test + * @copyright 2025 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \assignfeedback_file\event\feedback_downloaded + */ +final class feedback_downloaded_test extends \advanced_testcase { + /** + * Tests basic usage of the event, including creation, name, and description. + */ + public function test_feedback_downloaded_ok(): void { + $this->resetAfterTest(); + + $generator = self::getDataGenerator(); + $course = $generator->create_course(); + $assign = $generator->create_module('assign', ['course' => $course]); + $user = $generator->create_user(); + $this->setUser($user); + + $fs = get_file_storage(); + // We are not creating a real grade item, so I'm using 123 for its id. + $file = $fs->create_file_from_string([ + 'contextid' => \context_module::instance($assign->cmid)->id, + 'component' => 'assignfeedback_file', + 'filearea' => 'feedback_files', + 'itemid' => 123, + 'filepath' => '/', + 'filename' => 'testfile.txt', + ], 'hello world'); + $event = feedback_downloaded::create_for_file($file); + $this->assertInstanceOf(feedback_downloaded::class, $event); + $this->assertEquals(CONTEXT_MODULE, $event->get_context()->contextlevel); + $this->assertEquals($assign->cmid, $event->get_context()->instanceid); + $this->assertEquals(123, $event->objectid); + $this->assertEquals($file->get_id(), $event->other['fileid']); + $this->assertEquals('testfile.txt', $event->other['filename']); + $this->assertEquals('Feedback file downloaded', $event->get_name()); + + $this->assertEquals( + "The user with id '{$user->id}' downloaded feeedback file 'testfile.txt'" . + " ('{$file->get_id()}') for the assignment with course module id '{$assign->cmid}'.", + $event->get_description(), + ); + } + + /** + * Tests invalid missing fileid if you create manually (which you shouldn't). + */ + public function test_no_fileid(): void { + $this->expectExceptionMessageMatches('~other\[\'fileid\'\] must be set~'); + feedback_downloaded::create([ + 'context' => \context_system::instance(), + 'objectid' => 123, + 'other' => [ + 'filename' => 'testfile.txt', + ], + ]); + } + + /** + * Tests invalid missing filename if you create manually (which you shouldn't). + */ + public function test_no_filename(): void { + $this->expectExceptionMessageMatches('~other\[\'filename\'\] must be set~'); + feedback_downloaded::create([ + 'context' => \context_system::instance(), + 'objectid' => 123, + 'other' => [ + 'fileid' => 456, + ], + ]); + } +}