. /** * * @package local_treestudyplan * @copyright 2023 P.M. Kuipers * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace local_treestudyplan; require_once($CFG->libdir.'/externallib.php'); use \local_treestudyplan\courseinfo; use \local_treestudyplan\associationservice; use \local_treestudyplan\local\helpers\webservicehelper; use \local_treestudyplan\completionscanner; use \local_treestudyplan\gradingscanner; class courseservice extends \external_api { const CAP_EDIT = "local/treestudyplan:editstudyplan"; const CAP_VIEW = "local/treestudyplan:viewuserreports"; /************************ * * * list_courses * * * ************************/ public static function map_categories_parameters() { return new \external_function_parameters( [ "root_id" => new \external_value(PARAM_INT, 'root category to use as base', VALUE_DEFAULT), ] ); } public static function map_categories_returns() { return new \external_multiple_structure(static::map_category_structure(false)); } protected static function map_category_structure($lazy=false, $value=VALUE_REQUIRED) { $s = [ "id" => new \external_value(PARAM_INT, 'course category id'), "context_id" => new \external_value(PARAM_INT, 'course category context id'), "category" => contextinfo::structure(VALUE_OPTIONAL), "haschildren" => new \external_value(PARAM_BOOL, 'True if the category has child categories'), "hascourses" => new \external_value(PARAM_BOOL, 'True if the category contains courses'), "studyplancount" => new \external_value(PARAM_INT, 'number of linked studyplans', VALUE_OPTIONAL), ]; if (!$lazy > 0) { $s["courses"] = new \external_multiple_structure( courseinfo::editor_structure() ); $s["children"] = new \external_multiple_structure( static::map_category_structure(true)); } return new \external_single_structure($s, "CourseCat info", $value); } public static function map_categories($rootid = 0) { global $CFG, $DB; $root = \core_course_category::get($rootid); $context = $root->get_context(); // Make sure the user has access to the context for editing purposes. webservicehelper::require_capabilities(self::CAP_EDIT, $context); // Determine top categories from provided context. if ($root->id == 0) { // On the system level, determine the user's topmost allowed catecories. $usertop = \core_course_category::user_top(); if ($usertop->id == 0) { // Top category.. $children = $root->get_children(); // Returns a list of çore_course_category, let it overwrite $children. } else { $children = [$usertop]; } } else if ($root->is_uservisible()) { $children = [$root]; } foreach ($children as $cat) { $list[] = static::map_category($cat, false); } return $list; } public static function get_category_parameters() { return new \external_function_parameters( [ "id" => new \external_value(PARAM_INT, 'id of category'), ] ); } public static function get_category_returns() { return static::map_category_structure(false); } public static function get_category($id) { $cat = \core_course_category::get($id); return static::map_category($cat); } protected static function map_category(\core_course_category $cat, $lazy=false) { global $DB; $catcontext = $cat->get_context(); $ctxinfo = new contextinfo($catcontext); $children = $cat->get_children(); // Only shows children visible to the current user. $courses = $cat->get_courses(); $model = [ "id" => $cat->id, "context_id" => $catcontext->id, "category" => $ctxinfo->model(), "haschildren" => !empty($children), "hascourses" => !empty($courses), ]; if (!$lazy) { $model["courses"] = []; foreach ($courses as $course) { $courseinfo = new courseinfo($course->id); $model["courses"][] = $courseinfo->editor_model(); } $model["children"] = []; foreach ($children as $child) { $model["children"][] = static::map_category($child, true); } } return $model; } public static function list_accessible_categories_parameters() { return new \external_function_parameters( [ "operation" => new \external_value(PARAM_TEXT, 'type of operation ["view"|"edit"]', VALUE_DEFAULT), ] ); } public static function list_accessible_categories_returns() { return new \external_multiple_structure(static::map_category_structure(true)); } public static function list_accessible_categories($operation="edit") { if ($operation == "edit") { $capability = self::CAP_EDIT; } else { // Operation == "view" || default. $capability = self::CAP_VIEW; } $cats = static::categories_by_capability($capability); $list = []; /* @var $cat \core_course_category */ foreach ($cats as $cat) { $list[] = static::map_category($cat, true); } return $list; } public static function categories_by_capability($capability, \core_course_category $parent=null) { // List the categories in which the user has a specific capability. $list = []; // Initialize parent if needed. if ($parent == null) { $parent = \core_course_category::user_top(); if (has_capability($capability, $parent->get_context())) { $list[] = $parent; } } $children = $parent->get_children(); foreach ($children as $child) { // Check if we should add this category. if (has_capability($capability, $child->get_context())) { $list[] = $child; // For optimization purposes, we include all its children now, since they will have inherited the permission. // #PREMATURE_OPTIMIZATION ???. $list = array_merge($list, self::recursive_child_categories($child)); } else { if ($child->get_children_count() > 0) { $list = array_merge($list, self::categories_by_capability($capability, $child)); } } } return $list; } protected static function recursive_child_categories(\core_course_category $parent) { $list = []; $children = $parent->get_children(); foreach ($children as $child) { $list[] = $child; if ($child->get_children_count() > 0) { $list = array_merge($list, self::recursive_child_categories($child)); } } return $list; } public static function list_used_categories_parameters() { return new \external_function_parameters( [ "operation" => new \external_value(PARAM_TEXT, 'type of operation ["view"|"edit"]', VALUE_DEFAULT), ]); } public static function list_used_categories_returns() { return new \external_multiple_structure(static::map_category_structure(true)); } public static function list_used_categories($operation='edit') { global $DB; if ($operation == "edit") { $capability = self::CAP_EDIT; } else { // Operation == "view" || default. $capability = self::CAP_VIEW; } $contextids = []; $rs = $DB->get_recordset_sql("SELECT DISTINCT context_id, COUNT(*) as num FROM {local_treestudyplan} GROUP BY context_id"); foreach ($rs as $r) { $contextids[$r->context_id] = $r->num; } $rs->close(); // Now filter the categories that the user has acces to by the used context id's. // (That should filter out irrelevant stuff). $cats = static::categories_by_capability($capability); $list = []; foreach ($cats as $cat) { $count = 0; $ctxid = $cat->get_context()->id; if (array_key_exists($ctxid, $contextids)) { $count = $contextids[$ctxid]; } $o = static::map_category($cat, true); $o["studyplancount"] = $count; $list[] = $o; } return $list; } public static function list_accessible_categories_with_usage($operation='edit') { global $DB; if ($operation == "edit") { $capability = self::CAP_EDIT; } else { // Operation == "view" || default. $capability = self::CAP_VIEW; } // Retrieve context ids used. $contextids = []; $rs = $DB->get_recordset_sql("SELECT DISTINCT context_id, COUNT(*) as num FROM {local_treestudyplan} GROUP BY context_id"); foreach ($rs as $r) { $contextids[$r->context_id] = $r->num; } $rs->close(); // Now filter the categories that the user has acces to by the used context id's. // (That should filter out irrelevant stuff). $cats = static::categories_by_capability($capability); $list = []; foreach ($cats as $cat) { $count = 0; $ctxid = $cat->get_context()->id; if (array_key_exists($ctxid, $contextids)) { $count = $contextids[$ctxid]; } $o = new \stdClass(); $o->cat = $cat; $o->ctxid = $ctxid; $o->count = $count; $list[] = $o; } return $list; } /************************************** * * Progress scanners for teacherview * **************************************/ public static function scan_grade_progress_parameters() { return new \external_function_parameters( [ "gradeitemid" => new \external_value(PARAM_INT, 'Grade item ID to scan progress for', VALUE_DEFAULT), "studyplanid" => new \external_value(PARAM_INT, 'Study plan id to check progress in', VALUE_DEFAULT), ]); } public static function scan_grade_progress_returns() { return gradingscanner::structure(VALUE_REQUIRED); } public static function scan_grade_progress($gradeitemid, $studyplanid) { global $DB; // Verify access to the study plan. $o = studyplan::findById($studyplanid); webservicehelper::require_capabilities(self::CAP_VIEW, $o->context()); // Retrieve grade item. $gi = \grade_item::fetch(["id" => $gradeitemid]); // Validate course is linked to studyplan. $courseid = $gi->courseid; if (!$o->course_linked($courseid)) { throw new \webservice_access_exception("Course {$courseid} linked to grade item {$gradeitemid} is not linked to studyplan {$o->id()}"); } $scanner = new gradingscanner($gi); return $scanner->model(); } public static function scan_completion_progress_parameters() { return new \external_function_parameters( [ "criteriaid" => new \external_value(PARAM_INT, 'CriteriaID to scan progress for', VALUE_DEFAULT), "studyplanid" => new \external_value(PARAM_INT, 'Study plan id to check progress in', VALUE_DEFAULT), "courseid" => new \external_value(PARAM_INT, 'Course id of criteria', VALUE_DEFAULT), ]); } public static function scan_completion_progress_returns() { return completionscanner::structure(VALUE_REQUIRED); } public static function scan_completion_progress($criteriaid, $studyplanid, $courseid) { global $DB; // Verify access to the study plan. $o = studyplan::findById($studyplanid); webservicehelper::require_capabilities(self::CAP_VIEW, $o->context()); $crit = \completion_criteria::fetch(["id" => $criteriaid]); if (!$o->course_linked($courseid)) { throw new \webservice_access_exception("Course {$courseid} linked to criteria {$criteriaid} is not linked to studyplan {$o->id()}"); } $scanner = new completionscanner($crit, \get_course($courseid)); return $scanner->model(); } public static function scan_badge_progress_parameters() { return new \external_function_parameters( [ "badgeid" => new \external_value(PARAM_INT, 'Badge to scan progress for', VALUE_DEFAULT), "studyplanid" => new \external_value(PARAM_INT, 'Study plan id to limit progress search to (to determine which students to scan)', VALUE_DEFAULT), ]); } public static function scan_badge_progress_returns() { return new \external_single_structure([ "total" => new \external_value(PARAM_INT, 'Total number of students scanned'), "issued" => new \external_value(PARAM_INT, 'Number of issued badges'), ]); } public static function scan_badge_progress($badgeid, $studyplanid) { global $DB; // Check access to the study plan. $o = studyplan::findById($studyplanid); webservicehelper::require_capabilities(self::CAP_VIEW, $o->context()); // Validate that badge is linked to studyplan. if (!$o->badge_linked($badgeid)) { throw new \webservice_access_exception("Badge {$badgeid} is not linked to studyplan {$o->id()}"); } // Get badge info. $badge = new \core_badges\badge($badgeid); $badgeinfo = new badgeinfo($badge); // Get the connected users. $students = associationservice::all_associated($studyplanid); // Just get the user ids. $studentids = array_map(function ($a) { return $a["id"];}, $students); return [ "total" => count($studentids), "issued" => $badgeinfo->count_issued($studentids), ]; } }