diff --git a/course/format/classes/activityoverviewbase.php b/course/format/classes/activityoverviewbase.php index c0e734fa1aa..beb95a9992f 100644 --- a/course/format/classes/activityoverviewbase.php +++ b/course/format/classes/activityoverviewbase.php @@ -23,6 +23,8 @@ use core_courseformat\local\overview\overviewitem; use core_courseformat\output\local\overview\activityname; use core_courseformat\output\local\overview\overviewpage; use core_courseformat\base as courseformat; +use grade_item; +use grade_grade; /** * Base class for activity overview. @@ -170,4 +172,87 @@ abstract class activityoverviewbase { content: $completion, ); } + + /** + * Retrieves the grades overview items for the activity. + * + * Most activities will have none or one grade. However, some activities + * may have multiple grades, such as workshop or quiz. + * + * It is not recommended to override this method unless the plugin + * has specific requirements. Instead, plugins should override + * get_grade_item_names to provide the grade item names. + * + * @return overviewitem[] Array of overview items representing the grades. + */ + public function get_grades_overviews(): array { + global $CFG, $USER; + // This overview is to see the own grades, users with full gradebook + // access will see all grades in the gradebook. + if (has_capability('moodle/grade:viewall', $this->context)) { + return []; + } + if (!plugin_supports('mod', $this->cm->modname, FEATURE_GRADE_HAS_GRADE, false)) { + return []; + } + require_once($CFG->libdir . '/gradelib.php'); + + $items = grade_item::fetch_all([ + 'itemtype' => 'mod', + 'itemmodule' => $this->cm->modname, + 'iteminstance' => $this->cm->instance, + 'courseid' => $this->course->id, + ]); + if (empty($items)) { + return []; + } + + $itemnames = $this->get_grade_item_names($items); + $result = []; + foreach ($items as $item) { + // Plugins may decide to hide a specific grade item by not setting a name. + if (empty($itemnames[$item->id])) { + continue; + } + + $gradegrade = grade_grade::fetch(['itemid' => $item->id, 'userid' => $USER->id]); + + if ( + !$gradegrade + || ($gradegrade->is_hidden() && !has_capability('moodle/grade:viewhidden', $this->context)) + ) { + $result[] = new overviewitem( + name: $itemnames[$item->id], + value: '-', + content: '-', + ); + continue; + } + + $result[] = new overviewitem( + name: $itemnames[$item->id], + value: $gradegrade->finalgrade, + content: grade_format_gradevalue($gradegrade->finalgrade, $item), + ); + } + return $result; + } + + /** + * Retrieves the grade item names for the activity. + * + * By default, the overview will display the grade if the activities + * has only one grade item. The name of the grade item will be 'Grade'. + * For plugins with multiple grade items, the plugin must override this method + * and provide names for each grade item that want to be displayed. + * + * @param grade_item[] $items + * @return array the grade item names indexed by item id. + */ + protected function get_grade_item_names(array $items): array { + if (count($items) == 1) { + return [reset($items)->id => get_string('gradenoun')]; + } + return []; + } } diff --git a/course/format/classes/output/local/overview/overviewtable.php b/course/format/classes/output/local/overview/overviewtable.php index fa46d1f6c11..25dec10e47a 100644 --- a/course/format/classes/output/local/overview/overviewtable.php +++ b/course/format/classes/output/local/overview/overviewtable.php @@ -226,6 +226,13 @@ class overviewtable implements renderable, named_templatable { $row = array_merge($row, $overview->get_extra_overview_items($output)); + $gradeitems = $overview->get_grades_overviews(); + if (!empty($gradeitems)) { + foreach ($gradeitems as $gradeitem) { + $row[$gradeitem->get_name()] = $gradeitem; + } + } + // Actions are always the last column, if any. $row['actions'] = $overview->get_actions_overview($output); diff --git a/course/format/tests/activityoverviewbase_test.php b/course/format/tests/activityoverviewbase_test.php index 1c508ffbf68..8e5684bbdb8 100644 --- a/course/format/tests/activityoverviewbase_test.php +++ b/course/format/tests/activityoverviewbase_test.php @@ -31,6 +31,7 @@ final class activityoverviewbase_test extends \advanced_testcase { global $CFG; require_once($CFG->libdir . '/completionlib.php'); require_once($CFG->dirroot . '/course/format/tests/fixtures/fake_activityoverview.php'); + require_once($CFG->libdir . '/gradelib.php'); parent::setUpBeforeClass(); } @@ -166,4 +167,168 @@ final class activityoverviewbase_test extends \advanced_testcase { $this->assertEquals(null, $result->get_value()); $this->assertEquals('-', $result->get_content()); } + + /** + * Test get_grades_overviews method. + * + * @covers ::get_grades_overviews + */ + public function test_get_grades_overviews(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $generator = $this->getDataGenerator(); + $course = $this->getDataGenerator()->create_course(); + $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); + $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); + + // Create some modules. + $assign = $this->getDataGenerator()->create_module( + 'assign', + ['course' => $course->id] + ); + $workshop = $this->getDataGenerator()->create_module( + 'workshop', + ['course' => $course->id], + ['grade' => 100.0], + ); + $page = $this->getDataGenerator()->create_module( + 'page', + ['course' => $course->id] + ); + + // Assignments have one grade item. + $assignitems = \grade_item::fetch_all([ + 'itemtype' => 'mod', + 'itemmodule' => 'assign', + 'iteminstance' => (int) $assign->id, + 'courseid' => $course->id, + ]); + $gradegrade = new \grade_grade(); + $gradegrade->itemid = reset($assignitems)->id; + $gradegrade->userid = (int) $student->id; + $gradegrade->rawgrade = 88; + $gradegrade->finalgrade = 88; + $gradegrade->insert(); + + // Workshops have two grade items. + $workshopitems = array_values( + \grade_item::fetch_all([ + 'itemtype' => 'mod', + 'itemmodule' => 'workshop', + 'iteminstance' => (int) $workshop->id, + 'courseid' => $course->id, + ['grade' => 100.0], + ]) + ); + $gradegrade = new \grade_grade(); + $gradegrade->itemid = reset($workshopitems)->id; + $gradegrade->userid = (int) $student->id; + $gradegrade->rawgrade = 77; + $gradegrade->finalgrade = 77; + $gradegrade->insert(); + + // Validate student grades. + $this->setUser($student); + $modinfo = get_fast_modinfo($course); + + // Validate assign gradeitems. + $cm = $modinfo->get_cm($assign->cmid); + $overview = new \core_courseformat\fake_activityoverview($cm); + $result = $overview->get_grades_overviews(); + $this->assertCount(1, $result); + $this->assertEquals(get_string('gradenoun'), $result[0]->get_name()); + $this->assertEquals(88, $result[0]->get_value()); + $this->assertEquals('88.00', $result[0]->get_content()); + + // Validate workshop gradeitems (having two grade, they should return an empty array). + $cm = $modinfo->get_cm($workshop->cmid); + $overview = new \core_courseformat\fake_activityoverview($cm); + $result = $overview->get_grades_overviews(); + $this->assertEmpty($result); + + // Validate page has no gradeitems. + $cm = $modinfo->get_cm($page->cmid); + $overview = new \core_courseformat\fake_activityoverview($cm); + $result = $overview->get_grades_overviews(); + $this->assertEmpty($result); + + // Validate teacher does not has grade overiviews items. + $this->setUser($teacher); + $modinfo = get_fast_modinfo($course); + + // Validate assign gradeitems. + $cm = $modinfo->get_cm($assign->cmid); + $overview = new \core_courseformat\fake_activityoverview($cm); + $result = $overview->get_grades_overviews(); + $this->assertEmpty($result);; + + // Validate workshop gradeitems (having two grade, they should return an empty array). + $cm = $modinfo->get_cm($workshop->cmid); + $overview = new \core_courseformat\fake_activityoverview($cm); + $result = $overview->get_grades_overviews(); + $this->assertEmpty($result); + + // Validate page has no gradeitems. + $cm = $modinfo->get_cm($page->cmid); + $overview = new \core_courseformat\fake_activityoverview($cm); + $result = $overview->get_grades_overviews(); + $this->assertEmpty($result); + } + + /** + * Test get_grades_overviews when the grade item is hidden. + * + * @covers ::get_grades_overviews + */ + /** + * Test get_grades_overviews method. + * + * @covers ::get_grades_overviews + */ + public function test_get_grades_overviews_hidden(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $generator = $this->getDataGenerator(); + $course = $this->getDataGenerator()->create_course(); + $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); + + // Create some modules. + $assign = $this->getDataGenerator()->create_module( + 'assign', + ['course' => $course->id] + ); + + // Assignments have one grade item. + $assignitems = \grade_item::fetch_all([ + 'itemtype' => 'mod', + 'itemmodule' => 'assign', + 'iteminstance' => (int) $assign->id, + 'courseid' => $course->id, + ]); + $gradeitem = reset($assignitems); + $gradegrade = new \grade_grade(); + $gradegrade->itemid = $gradeitem->id; + $gradegrade->userid = (int) $student->id; + $gradegrade->rawgrade = 88; + $gradegrade->finalgrade = 88; + $gradegrade->insert(); + + // Hide some grades. + $gradeitem->set_hidden(1, true); + + // Validate student grades. + $this->setUser($student); + $modinfo = get_fast_modinfo($course); + + // Validate assign gradeitems. + $cm = $modinfo->get_cm($assign->cmid); + $overview = new \core_courseformat\fake_activityoverview($cm); + $result = $overview->get_grades_overviews(); + $this->assertCount(1, $result); + $this->assertEquals(get_string('gradenoun'), $result[0]->get_name()); + $this->assertEquals('-', $result[0]->get_value()); + $this->assertEquals('-', $result[0]->get_content()); + } }