From 6cf7908652c294e839788b710d9c80834868419c Mon Sep 17 00:00:00 2001 From: Mihail Geshoski Date: Mon, 31 Jul 2023 13:29:54 +0200 Subject: [PATCH] MDL-77035 grades: WS that returns the grade tree structure in a course --- grade/classes/external/get_grade_tree.php | 121 +++++++++++++++++++ grade/tests/external/get_grade_tree_test.php | 119 ++++++++++++++++++ lib/db/services.php | 6 + version.php | 2 +- 4 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 grade/classes/external/get_grade_tree.php create mode 100644 grade/tests/external/get_grade_tree_test.php diff --git a/grade/classes/external/get_grade_tree.php b/grade/classes/external/get_grade_tree.php new file mode 100644 index 00000000000..7eb48635413 --- /dev/null +++ b/grade/classes/external/get_grade_tree.php @@ -0,0 +1,121 @@ +. + +namespace core_grades\external; + +use core_external\external_api; +use core_external\external_function_parameters; +use core_external\external_value; + +defined('MOODLE_INTERNAL') || die; + +require_once($CFG->dirroot.'/grade/lib.php'); + +/** + * Web service to return the grade tree structure for a given course. + * + * @package core_grades + * @copyright 2023 Mihail Geshoski + * @category external + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class get_grade_tree extends external_api { + + /** + * Returns description of method parameters. + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters ( + [ + 'courseid' => new external_value(PARAM_INT, 'Course ID', VALUE_REQUIRED) + ] + ); + } + + /** + * Given a course ID, return the grade tree structure for that course. + * + * @param int $courseid The course ID. + * @return string JSON encoded data representing the course grade tree structure. + */ + public static function execute(int $courseid): string { + + $params = self::validate_parameters( + self::execute_parameters(), + [ + 'courseid' => $courseid + ] + ); + + $context = \context_course::instance($params['courseid']); + parent::validate_context($context); + // Make sure that the user has the capability to view the full grade tree in the course. + require_capability('moodle/grade:viewall', $context); + // Get the course grade category. + $coursegradecategory = \grade_category::fetch_course_category($params['courseid']); + $coursegradetree = self::generate_course_grade_tree($coursegradecategory); + + return json_encode($coursegradetree); + } + + /** + * Describes the return structure. + * + * @return external_value + */ + public static function execute_returns(): external_value { + return new external_value(PARAM_RAW, 'JSON encoded data representing the course grade tree structure.'); + } + + /** + * Recursively generates the course grade tree structure. + * + * @param \grade_category $gradecategory The course grade category. + * @return array The course grade tree structure. + */ + private static function generate_course_grade_tree(\grade_category $gradecategory): array { + $gradecategorydata = [ + 'id' => $gradecategory->id, + 'name' => $gradecategory->get_name(), + 'iscategory' => true, + 'haschildcategories' => false + ]; + + // Get the children of the grade category. + if ($gradecategorychildren = $gradecategory->get_children()) { + foreach ($gradecategorychildren as $child) { + // If the child is a grade category, recursively generate the grade tree structure for that category. + if ($child['object'] instanceof \grade_category) { + $gradecategorydata['haschildcategories'] = true; + $gradecategorydata['children'][] = self::generate_course_grade_tree($child['object']); + } else { // Otherwise, add the grade item to the grade tree structure. + $gradecategorydata['children'][] = [ + 'id' => $child['object']->id, + 'name' => $child['object']->get_name(), + 'iscategory' => false, + 'children' => null + ]; + } + } + } else { // If the grade category has no children, set the children property to null. + $gradecategorydata['children'] = null; + } + + return $gradecategorydata; + } +} diff --git a/grade/tests/external/get_grade_tree_test.php b/grade/tests/external/get_grade_tree_test.php new file mode 100644 index 00000000000..8f6c4df7473 --- /dev/null +++ b/grade/tests/external/get_grade_tree_test.php @@ -0,0 +1,119 @@ +. + +namespace core_grades\external; + +use core_external\external_api; + +defined('MOODLE_INTERNAL') || die; + +global $CFG; + +require_once($CFG->dirroot . '/webservice/tests/helpers.php'); + +/** + * Unit tests for core_grades\external\get_grade_tree. + * + * @package core_grades + * @category external + * @copyright 2023 Mihail Geshoski + * @covers \core_grades\external\get_grade_tree + */ +class get_grade_tree_test extends \externallib_advanced_testcase { + + /** + * Test the return value of the external function. + * + * @covers ::execute + * @return void + */ + public function test_execute(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + $course = $this->getDataGenerator()->create_course(['fullname' => 'Course']); + $coursegradecategory = \grade_category::fetch_course_category($course->id); + // Create a grade item 'Grade item' and grade category 'Category 1' within the course grade category. + $gradeitem = $this->getDataGenerator()->create_grade_item( + ['courseid' => $course->id, 'itemname' => 'Grade item']); + $gradecategory1 = $this->getDataGenerator()->create_grade_category( + ['courseid' => $course->id, 'fullname' => 'Category 1']); + // Create a grade item 'Grade item 1' and grade category 'Category 2' within 'Category 1'. + $gradeitem1 = $this->getDataGenerator()->create_grade_item( + ['courseid' => $course->id, 'itemname' => 'Grade item 1', 'categoryid' => $gradecategory1->id]); + $gradecategory2 = $this->getDataGenerator()->create_grade_category( + ['courseid' => $course->id, 'fullname' => 'Category 2', 'parent' => $gradecategory1->id]); + // Create a grade item 'Grade item 2' and grade category 'Category 3' (with no children) within 'Category 2'. + $gradeitem2 = $this->getDataGenerator()->create_grade_item( + ['courseid' => $course->id, 'itemname' => 'Grade item 2', 'categoryid' => $gradecategory2->id]); + $gradecategory3 = $this->getDataGenerator()->create_grade_category( + ['courseid' => $course->id, 'fullname' => 'Category 3', 'parent' => $gradecategory2->id]); + + $result = get_grade_tree::execute($course->id); + $result = external_api::clean_returnvalue(get_grade_tree::execute_returns(), $result); + + $expected = json_encode([ + 'id' => $coursegradecategory->id, + 'name' => 'Course', + 'iscategory' => true, + 'haschildcategories' => true, + 'children' => [ + [ + 'id' => $gradeitem->id, + 'name' => 'Grade item', + 'iscategory' => false, + 'children' => null + ], + [ + 'id' => $gradecategory1->id, + 'name' => 'Category 1', + 'iscategory' => true, + 'haschildcategories' => true, + 'children' => [ + [ + 'id' => $gradeitem1->id, + 'name' => 'Grade item 1', + 'iscategory' => false, + 'children' => null + ], + [ + 'id' => $gradecategory2->id, + 'name' => 'Category 2', + 'iscategory' => true, + 'haschildcategories' => true, + 'children' => [ + [ + 'id' => $gradeitem2->id, + 'name' => 'Grade item 2', + 'iscategory' => false, + 'children' => null + ], + [ + 'id' => $gradecategory3->id, + 'name' => 'Category 3', + 'iscategory' => true, + 'haschildcategories' => false, + 'children' => null + ] + ] + ] + ] + ] + ] + ]); + + $this->assertEquals($expected, $result); + } +} diff --git a/lib/db/services.php b/lib/db/services.php index 503c4743877..a83bb7c1dc5 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -1008,6 +1008,12 @@ $functions = array( 'ajax' => true, 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE], ], + 'core_grades_get_grade_tree' => [ + 'classname' => 'core_grades\external\get_grade_tree', + 'description' => 'Get the grade tree structure for a course', + 'type' => 'read', + 'ajax' => true, + ], 'core_grading_get_definitions' => array( 'classname' => 'core_grading_external', 'methodname' => 'get_definitions', diff --git a/version.php b/version.php index 67c1425e22a..382fe39872b 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2023092000.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2023092000.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.3dev+ (Build: 20230920)'; // Human-friendly version name