From f3fd9b18368f26b8b6b1ef15b9a96d8bcad4cc90 Mon Sep 17 00:00:00 2001 From: Mi Jia Looi Date: Tue, 15 Jul 2025 16:12:15 +1000 Subject: [PATCH 001/458] MDL-86017 mod_folder: Cast customdata to object in folder_cm_info_view() --- mod/folder/lib.php | 2 +- mod/folder/tests/lib_test.php | 52 +++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/mod/folder/lib.php b/mod/folder/lib.php index 82393fc72ea1..1b47af5fb182 100644 --- a/mod/folder/lib.php +++ b/mod/folder/lib.php @@ -459,7 +459,7 @@ function folder_cm_info_view(cm_info $cm) { // Restore folder object from customdata. // Note the field 'customdata' is not empty IF AND ONLY IF we display contens inline. // Otherwise the content is default. - $folder = $cm->customdata; + $folder = (object) $cm->get_custom_data(); $folder->id = (int)$cm->instance; $folder->course = (int)$cm->course; $folder->display = FOLDER_DISPLAY_INLINE; diff --git a/mod/folder/tests/lib_test.php b/mod/folder/tests/lib_test.php index 7db30b491119..155c4938cf83 100644 --- a/mod/folder/tests/lib_test.php +++ b/mod/folder/tests/lib_test.php @@ -402,4 +402,56 @@ public static function folder_get_recent_mod_activity_provider(): array { ], ]; } + + /** + * Test that folder_cm_info_view handles custom data correctly after override_customdata() changes it from object to array. + * @covers ::folder_cm_info_view() + * + * @return void + */ + public function test_folder_cm_info_view(): void { + $course = $this->getDataGenerator()->create_course(); + $folderinline = $this->getDataGenerator()->create_module('folder', ['course' => $course->id, + 'name' => 'testfolder', 'display' => FOLDER_DISPLAY_INLINE]); + + $cm = get_fast_modinfo($course)->get_cm($folderinline->cmid); + $this->assertIsObject($cm->customdata); + $cm->override_customdata('showexpanded', 1); + $cm->override_customdata('showdownloadfolder', 1); + $cm->override_customdata('forcedownload', 1); + + $expecteddata = [ + 'showexpanded' => 1, + 'showdownloadfolder' => 1, + 'forcedownload' => 1, + ]; + $this->assertIsArray($cm->get_custom_data()); + $this->assertEquals($expecteddata, $cm->get_custom_data()); + + // Add a test file to the folder activity. + $context = context_module::instance($cm->id); + $fs = get_file_storage(); + $filerecord = [ + 'contextid' => $context->id, + 'component' => 'mod_folder', + 'filearea' => 'content', + 'itemid' => 0, + 'filepath' => '/', + 'filename' => 'testfile.txt', + ]; + $fs->create_file_from_string($filerecord, 'Test file content'); + + // Verify content after custom data overrides. + // The download option should be present when showdownloadfolder=1 and the folder has a file. + folder_cm_info_view($cm); + $content = $cm->content; + $this->assertStringContainsString($cm->get_formatted_name(['escape' => false]), $content); + $this->assertStringContainsString('mod/folder/download_folder.php', $content); + + // The download option should not be present when showdownloadfolder=0 and the folder has a file. + $cm->override_customdata('showdownloadfolder', 0); + folder_cm_info_view($cm); + $contentnodownload = $cm->content; + $this->assertStringNotContainsString('mod/folder/download_folder.php', $contentnodownload); + } } From 29d322526f3cc64f3a92d99fbea1a3fb3ec88d5f Mon Sep 17 00:00:00 2001 From: dustinhuynh Date: Tue, 5 Aug 2025 16:11:34 +1000 Subject: [PATCH 002/458] MDL-86058 core_task: enable disable tasks with a pending upgrade --- admin/cli/scheduled_task.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/admin/cli/scheduled_task.php b/admin/cli/scheduled_task.php index d75b4f8e0fbd..940f6df22bd3 100644 --- a/admin/cli/scheduled_task.php +++ b/admin/cli/scheduled_task.php @@ -125,11 +125,6 @@ exit(0); } -if (moodle_needs_upgrading()) { - mtrace("Moodle upgrade pending, cannot manage tasks."); - exit(1); -} - if ($disable = $options['disable']) { if (!$task = \core\task\manager::get_scheduled_task($disable)) { mtrace("Task '$disable' not found"); @@ -157,6 +152,11 @@ exit(1); } } else if ($execute = $options['execute']) { + if (moodle_needs_upgrading()) { + mtrace("Moodle upgrade pending, cannot execute tasks."); + exit(1); + } + if (!$task = \core\task\manager::get_scheduled_task($execute)) { mtrace("Task '$execute' not found"); exit(1); From 161837e393b84ce43525eb88250689bc059b3f0a Mon Sep 17 00:00:00 2001 From: Jayce Birrell Date: Thu, 2 Oct 2025 14:39:41 +0930 Subject: [PATCH 003/458] MDL-86802 core: make CURLOPT_USERPWD optional for put() Microsoft does not always allow an Authorization header when using PUT but the put() method sets one by default - https://learn.microsoft.com/en-us/graph/api/driveitem-createuploadsession?view=graph-rest-1.0#remarks. I've included an optional argument to prevent this, but the default value will maintain the pre-existing logic to avoid other regressions. --- lib/filelib.php | 5 +++-- repository/onedrive/lib.php | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/filelib.php b/lib/filelib.php index edd075ed3b35..21217ea165d3 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -4166,9 +4166,10 @@ public function download_one($url, $params, $options = array()) { * @param string $url * @param array $params * @param array $options + * @param bool $includeuserpwd Whether to include CURLOPT_USERPWD if not already set * @return ?string */ - public function put($url, $params = array(), $options = array()) { + public function put($url, $params = [], $options = [], $includeuserpwd = true) { $file = ''; $fp = false; if (isset($params['file'])) { @@ -4182,7 +4183,7 @@ public function put($url, $params = array(), $options = array()) { } else { return null; } - if (!isset($this->options['CURLOPT_USERPWD'])) { + if (!isset($this->options['CURLOPT_USERPWD']) && $includeuserpwd) { $this->setopt(array('CURLOPT_USERPWD' => 'anonymous: noreply@moodle.org')); } } else { diff --git a/repository/onedrive/lib.php b/repository/onedrive/lib.php index f211cb50073a..828709923ca8 100644 --- a/repository/onedrive/lib.php +++ b/repository/onedrive/lib.php @@ -792,7 +792,7 @@ protected function get_mimetype_from_filename($filename) { protected function upload_file(\repository_onedrive\rest $service, \curl $curl, \curl $authcurl, $filepath, $mimetype, $parentid, $filename) { // Start an upload session. - // Docs https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/item_createuploadsession link. + // Docs https://learn.microsoft.com/en-us/graph/api/driveitem-createuploadsession?view=graph-rest-1.0 link. $params = ['parentid' => $parentid, 'filename' => urlencode($filename)]; $behaviour = [ 'item' => [ "@microsoft.graph.conflictBehavior" => "rename" ] ]; @@ -813,7 +813,8 @@ protected function upload_file(\repository_onedrive\rest $service, \curl $curl, $curlinstance->setHeader('Content-type: ' . $mimetype); $size = filesize($filepath); $curlinstance->setHeader('Content-Range: bytes 0-' . ($size - 1) . '/' . $size); - $response = $curlinstance->put($created->uploadUrl, $options); + // Microsoft does not allow authorization headers for PUT, so we do not include them here. + $response = $curlinstance->put($created->uploadUrl, $options, [], false); // False = do not include auth header. if ($curlinstance->errno == 0) { $response = json_decode($response); } From b458318d1300f686dde50fb1364d82a4e8e1f005 Mon Sep 17 00:00:00 2001 From: Stefan Hanauska Date: Fri, 29 Aug 2025 18:05:02 +0200 Subject: [PATCH 004/458] MDL-68062 badges: Allow revoke by every user with the correct role Co-authored-by: Rajneel Totaram --- badges/award.php | 2 +- badges/lib/awardlib.php | 32 +++++++++++++------------- badges/tests/behat/award_badge.feature | 18 ++++++++++++--- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/badges/award.php b/badges/award.php index 7b71559c2b23..564c9eb1c78c 100644 --- a/badges/award.php +++ b/badges/award.php @@ -207,7 +207,7 @@ $users = $existingselector->get_selected_users(); foreach ($users as $user) { - if (!process_manual_revoke($user->id, $USER->id, $issuerrole->roleid, $badgeid)) { + if (!process_manual_revoke($user->id, 0, $issuerrole->roleid, $badgeid)) { echo $OUTPUT->error_text(get_string('error:cannotrevokebadge', 'badges')); } } diff --git a/badges/lib/awardlib.php b/badges/lib/awardlib.php index f085c56cd01e..89d16ad03374 100644 --- a/badges/lib/awardlib.php +++ b/badges/lib/awardlib.php @@ -290,27 +290,27 @@ function process_manual_award($recipientid, $issuerid, $issuerrole, $badgeid) { /** * Manually revoke awarded badges. * - * @param int $recipientid - * @param int $issuerid - * @param int $issuerrole - * @param int $badgeid + * @param int $recipientid User ID of the recipient + * @param int $issuerid User ID of the issuer (if 0, issuer will be ignored) + * @param int $issuerrole Role of the issuer + * @param int $badgeid ID of the badge * @return bool */ function process_manual_revoke($recipientid, $issuerid, $issuerrole, $badgeid) { global $DB; - $params = array( - 'badgeid' => $badgeid, - 'issuerid' => $issuerid, - 'issuerrole' => $issuerrole, - 'recipientid' => $recipientid - ); + $params = [ + 'badgeid' => $badgeid, + 'issuerrole' => $issuerrole, + 'recipientid' => $recipientid, + ]; + if (!empty($issuerid)) { + $params['issuerid'] = $issuerid; + } if ($DB->record_exists('badge_manual_award', $params)) { - if ($DB->delete_records('badge_manual_award', array('badgeid' => $badgeid, - 'issuerid' => $issuerid, - 'recipientid' => $recipientid)) - && $DB->delete_records('badge_issued', array('badgeid' => $badgeid, - 'userid' => $recipientid))) { - + if ( + $DB->delete_records('badge_manual_award', $params) && + $DB->delete_records('badge_issued', ['badgeid' => $badgeid, 'userid' => $recipientid]) + ) { // Trigger event, badge revoked. $badge = new \badge($badgeid); $eventparams = array( diff --git a/badges/tests/behat/award_badge.feature b/badges/tests/behat/award_badge.feature index f5487fefe95a..9ef45afffb6a 100644 --- a/badges/tests/behat/award_badge.feature +++ b/badges/tests/behat/award_badge.feature @@ -11,11 +11,13 @@ Feature: Award badges And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | + | teacher2 | Teacher | 2 | teacher2@example.com | | student1 | Student | 1 | student1@example.com | | student2 | Student | 2 | student2@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | + | teacher2 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | And the following "activity" exists: @@ -371,14 +373,24 @@ Feature: Award badges And I am on "Course 1" course homepage And I navigate to "Badges" in current page administration And I follow "Course Badge" - Then I should see "Recipients (2)" And I select "Recipients (2)" from the "jump" singleselect And I press "Award badge" And I set the field "existingrecipients[]" to "Student 2 (student2@example.com)" And I press "Revoke badge" + And I am on "Course 1" course homepage + And I navigate to "Badges" in current page administration + And I follow "Course Badge" + Then I should see "Recipients (1)" + And I log out + # Now attempt to revoke a badge as another teacher. + And I am on the "Course 1" "course" page logged in as "teacher2" + And I navigate to "Badges" in current page administration + And I follow "Course Badge" + And I select "Recipients (1)" from the "jump" singleselect + And I press "Award badge" And I set the field "existingrecipients[]" to "Student 1 (student1@example.com)" - When I press "Revoke badge" + And I press "Revoke badge" And I am on "Course 1" course homepage And I navigate to "Badges" in current page administration And I follow "Course Badge" - Then I should see "Recipients (0)" + And I should see "Recipients (0)" From b6f7d9b1927a1bbd30a0fa4a8c996a85dfe044bd Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Tue, 11 Nov 2025 12:35:25 +0000 Subject: [PATCH 005/458] MDL-87056 enrol: update instance modified date when toggling status. --- lib/enrollib.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/enrollib.php b/lib/enrollib.php index c7014a585fe2..b7c5b5fd5adf 100644 --- a/lib/enrollib.php +++ b/lib/enrollib.php @@ -2714,6 +2714,7 @@ public function update_status($instance, $newstatus) { global $DB; $instance->status = $newstatus; + $instance->timemodified = time(); $DB->update_record('enrol', $instance); // Dispatch the hook for post enrol status update actions. From 4e85aecde808ffd1ee2508486efd49e7cf3808cf Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Fri, 26 Sep 2025 12:56:04 +0100 Subject: [PATCH 006/458] MDL-86565 courseformat: don't link to sections user cannot see. --- .../format/classes/output/local/content/section/header.php | 2 +- course/format/classes/output/local/state/section.php | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/course/format/classes/output/local/content/section/header.php b/course/format/classes/output/local/content/section/header.php index f4f3590656cd..2781a4273e3e 100644 --- a/course/format/classes/output/local/content/section/header.php +++ b/course/format/classes/output/local/content/section/header.php @@ -84,7 +84,7 @@ public function export_for_template(\renderer_base $output): stdClass { } else { if (is_null($format->get_sectionid()) || $format->get_sectionid() != $section->id) { // All sections are displayed. - if (!$data->editing) { + if (!$data->editing && $section->uservisible) { $data->title = $output->section_title($section, $course); } else { $data->title = $output->section_title_without_link($section, $course); diff --git a/course/format/classes/output/local/state/section.php b/course/format/classes/output/local/state/section.php index 96355dc401ff..7c62baeaa131 100644 --- a/course/format/classes/output/local/state/section.php +++ b/course/format/classes/output/local/state/section.php @@ -83,7 +83,6 @@ public function export_for_template(\renderer_base $output): stdClass { 'rawtitle' => $section->name, 'cmlist' => [], 'visible' => !empty($section->visible), - 'sectionurl' => course_get_url($course, $section->section, ['navigation' => true])?->out(false), 'current' => $format->is_section_current($section), 'indexcollapsed' => $indexcollapsed, 'contentcollapsed' => $contentcollapsed, @@ -94,6 +93,10 @@ public function export_for_template(\renderer_base $output): stdClass { 'parentsectionid' => $section->get_component_instance()?->get_parent_section()?->id, ]; + if ($section->uservisible) { + $data->sectionurl = course_get_url($course, $section->section, ['navigation' => true])?->out(false); + } + if (empty($modinfo->sections[$section->section])) { return $data; } From 6f62b7eaa597bcc520bb6f2528670a7371b64be9 Mon Sep 17 00:00:00 2001 From: Santosh Nagargoje Date: Tue, 18 Nov 2025 00:13:09 +0530 Subject: [PATCH 007/458] MDL-86749 questions: Added index on hashcode column Identified performance bottlenecks in clear_cached_data() from mod/quiz/report/statistics/report.php. The delete_records() calls on question_statistics, question_response_analysis, and quiz_statistics tables use the hashcode column for filtering, which becomes inefficient with large datasets. --- lib/db/install.xml | 8 +++++++- lib/db/upgrade.php | 22 ++++++++++++++++++++++ mod/quiz/report/statistics/db/install.xml | 5 ++++- mod/quiz/report/statistics/db/upgrade.php | 16 ++++++++++++++++ mod/quiz/report/statistics/version.php | 2 +- version.php | 2 +- 6 files changed, 51 insertions(+), 4 deletions(-) diff --git a/lib/db/install.xml b/lib/db/install.xml index 96474f3943e8..888dcb6033c0 100644 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -1,5 +1,5 @@ - @@ -1668,6 +1668,9 @@ + + + @@ -1686,6 +1689,9 @@ + + +
diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 060fce84f33d..9a8b622e8211 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -1872,5 +1872,27 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2025041403.05); } + if ($oldversion < 2025041403.08) { + // Define index hashcode (not unique) to be added to question_response_analysis. + $table = new xmldb_table('question_response_analysis'); + $index = new xmldb_index('hashcode', XMLDB_INDEX_NOTUNIQUE, ['hashcode']); + + // Conditionally launch add index hashcode. + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + // Define index hashcode (not unique) to be added to question_statistics. + $table = new xmldb_table('question_statistics'); + $index = new xmldb_index('hashcode', XMLDB_INDEX_NOTUNIQUE, ['hashcode']); + + // Conditionally launch add index hashcode. + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + // Main savepoint reached. + upgrade_main_savepoint(true, 2025041403.08); + } + return true; } diff --git a/mod/quiz/report/statistics/db/install.xml b/mod/quiz/report/statistics/db/install.xml index b53f0e1e3694..ab1f59e39836 100644 --- a/mod/quiz/report/statistics/db/install.xml +++ b/mod/quiz/report/statistics/db/install.xml @@ -1,5 +1,5 @@ - @@ -29,6 +29,9 @@ + + +
diff --git a/mod/quiz/report/statistics/db/upgrade.php b/mod/quiz/report/statistics/db/upgrade.php index 11f213de1bf4..b012614b118d 100644 --- a/mod/quiz/report/statistics/db/upgrade.php +++ b/mod/quiz/report/statistics/db/upgrade.php @@ -26,6 +26,8 @@ * Quiz statistics report upgrade code. */ function xmldb_quiz_statistics_upgrade($oldversion) { + global $CFG, $DB; + $dbman = $DB->get_manager(); // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. @@ -41,5 +43,19 @@ function xmldb_quiz_statistics_upgrade($oldversion) { // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. + if ($oldversion < 2025041401) { + // Define index hashcode (not unique) to be added to quiz_statistics. + $table = new xmldb_table('quiz_statistics'); + $index = new xmldb_index('hashcode', XMLDB_INDEX_NOTUNIQUE, ['hashcode']); + + // Conditionally launch add index hashcode. + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + // Statistics savepoint reached. + upgrade_plugin_savepoint(true, 2025041401, 'quiz', 'statistics'); + } + return true; } diff --git a/mod/quiz/report/statistics/version.php b/mod/quiz/report/statistics/version.php index 4fef3de67d22..64b74ffda627 100644 --- a/mod/quiz/report/statistics/version.php +++ b/mod/quiz/report/statistics/version.php @@ -24,6 +24,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2025041400; +$plugin->version = 2025041401; $plugin->requires = 2025040800; $plugin->component = 'quiz_statistics'; diff --git a/version.php b/version.php index 32dc439b4a2f..2efcd0add91b 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2025041403.07; // 20250414 = branching date YYYYMMDD - do not modify! +$version = 2025041403.08; // 20250414 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '5.0.3+ (Build: 20251113)'; // Human-friendly version name From aee6e963f2414697cf20677714a062a8109d33e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20v=2E=20L=C3=B6wis?= Date: Wed, 10 Sep 2025 10:36:40 +0200 Subject: [PATCH 008/458] MDL-86523 backup: update context only if also updating category. --- backup/moodle2/restore_stepslib.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php index c34d34904138..e7270333b9ca 100644 --- a/backup/moodle2/restore_stepslib.php +++ b/backup/moodle2/restore_stepslib.php @@ -6449,15 +6449,15 @@ public function process_question_set_reference($data) { ) { $newcategoryid = $this->get_mappingid('question_category', $oldcategoryid); $filtercondition['filter']['category']['values'][0] = $newcategoryid; - } - if ($context = $this->get_mappingid('context', $data->questionscontextid)) { - $data->questionscontextid = $context; - } else { - $this->log('question_set_reference with old id ' . $data->id . - ' referenced question context ' . $data->questionscontextid . - ' which was not included in the backup. Therefore, this has been ' . - ' restored with the old questionscontextid.', backup::LOG_WARNING); + if ($context = $this->get_mappingid('context', $data->questionscontextid)) { + $data->questionscontextid = $context; + } else { + $this->log('question_set_reference with old id ' . $data->id . + ' referenced question context ' . $data->questionscontextid . + ' which was not included in the backup. Therefore, this has been ' . + ' restored with the old questionscontextid.', backup::LOG_WARNING); + } } $filtercondition['cat'] = implode(',', [ From d371441c6289e7ee36741b8b44ea671f0bce8357 Mon Sep 17 00:00:00 2001 From: Leon Stringer Date: Mon, 18 Aug 2025 11:32:41 +0100 Subject: [PATCH 009/458] MDL-84898 core: Improve prohibit performance Change the query used to check if a user has a capability but is assigned a role that prohibits that capability. This is based on the query added by Petr Skoda for MDL-57027. --- lib/accesslib.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/accesslib.php b/lib/accesslib.php index 3c6df9dfbc57..646fe1dfa045 100644 --- a/lib/accesslib.php +++ b/lib/accesslib.php @@ -3796,14 +3796,13 @@ function get_with_capability_join(context $context, $capability, $useridcolumn) AND roleid IN (" . implode(',', array_keys($prohibited[$cap])) . "))"; } else { - $unions[] = "SELECT userid - FROM {role_assignments} - WHERE contextid IN ($ctxids) AND roleid IN (" . implode(',', array_keys($needed[$cap])) . ") - AND userid NOT IN ( - SELECT userid - FROM {role_assignments} - WHERE contextid IN ($ctxids) - AND roleid IN (" . implode(',', array_keys($prohibited[$cap])) . "))"; + $unions[] = "SELECT ra.userid + FROM {role_assignments} ra + LEFT JOIN {role_assignments} rap ON (rap.userid = ra.userid + AND rap.contextid IN ($ctxids) + AND rap.roleid IN (" . implode(',', array_keys($prohibited[$cap])) . ")) + WHERE ra.contextid IN ($ctxids) AND ra.roleid IN (" . implode(',', array_keys($needed[$cap])) . ") + AND rap.id IS NULL"; } } } From 773823a0825530d7dc485e3ed2b81cedf780f6bf Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Wed, 26 Nov 2025 11:38:21 +0000 Subject: [PATCH 010/458] MDL-87188 gradereport_grader: prevent overlapping collapsed columns. --- .../grader/templates/collapse/collapseresultitems.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grade/report/grader/templates/collapse/collapseresultitems.mustache b/grade/report/grader/templates/collapse/collapseresultitems.mustache index e008ba811c1c..cb6e814637c5 100644 --- a/grade/report/grader/templates/collapse/collapseresultitems.mustache +++ b/grade/report/grader/templates/collapse/collapseresultitems.mustache @@ -26,7 +26,7 @@ "category": "Hitchhikers grade category" } }} -
  • +