. /** * * @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 \grade_item; // $gi->courseid, . // $gi->itemmodule, . // $gi->iteminstance. class gradingscanner { private static $mod_supported = []; private static $course_students = []; private $scanner = null; private $gi = null; private $pending_cache = []; public static function supported($mod) { if (!array_key_exists($mod, self::$mod_supported)) { self::$mod_supported[$mod] = class_exists("\local_treestudyplan\\local\\ungradedscanners\\{$mod}_scanner"); } return self::$mod_supported[$mod]; } public static function get_course_students($courseid) { global $CFG; if (!array_key_exists($courseid, self::$course_students)) { $students = []; $context = \context_course::instance($courseid); foreach (explode(', ', $CFG->gradebookroles) as $roleid) { $roleid = trim($roleid); $students = array_keys(get_role_users($roleid, $context, false, 'u.id', 'u.id ASC')); } self::$course_students[$courseid] = $students; } return self::$course_students[$courseid]; } public function __construct(grade_item $gi) { $this->courseid = $gi->courseid; $this->gi = $gi; if (self::supported($gi->itemmodule)) { $scannerclass = "\local_treestudyplan\\local\ungradedscanners\\{$gi->itemmodule}_scanner"; $this->scanner = new $scannerclass($gi); } } public function is_available() { return $this->scanner !== null; } public function pending($userid) { if (!array_key_exists($userid, $this->pending_cache)) { if ($this->scanner === null) { $this->pending_cache[$userid] = false; } else { $this->pending_cache[$userid] = $this->scanner->has_ungraded_submission($userid);; } } return $this->pending_cache[$userid]; } public static function structure($value=VALUE_OPTIONAL) { return new \external_single_structure([ "ungraded" => new \external_value(PARAM_INT, 'number of ungraded submissions'), "completed" => new \external_value(PARAM_INT, 'number of completed students'), "completed_pass" => new \external_value(PARAM_INT, 'number of completed-pass students'), "completed_fail" => new \external_value(PARAM_INT, 'number of completed-fail students'), "students" => new \external_value(PARAM_INT, 'number of students that should submit'), ], "details about gradable submissions", $value); } public function model() { // Upda. $students = self::get_course_students($this->courseid); $completed = 0; $ungraded = 0; $completed_pass = 0; $completed_fail = 0; foreach ($students as $userid) { if ($this->pending($userid)) { // First check if the completion needs grading. $ungraded++; } else { $grade = $this->gi->get_final($userid); if (!is_numeric($grade->finalgrade) && empty($grade->finalgrade)) { //skip. } else { //compare grade to minimum grade. if ($this->grade_passed($grade)) { $completed_pass++; } else { $completed_fail++; } } } } return [ 'ungraded' => $ungraded, 'completed' => $completed, 'completed_pass' => $completed_pass, 'completed_fail' => $completed_fail, 'students' => count($students), ]; } // Function copied from bistate aggregator to avoid reference mazes. private function grade_passed($grade) { global $DB; $table = "local_treestudyplan_gradecfg"; // first determine if we have a grade_config for this scale or this maximum grade. $finalgrade = $grade->finalgrade; $scale = $this->gi->load_scale(); if ( isset($scale)) { $gradecfg = $DB->get_record($table, ["scale_id"=>$scale->id]); } else if ($this->gi->grademin == 0) { $gradecfg = $DB->get_record($table, ["grade_points"=>$this->gi->grademax]); } else { $gradecfg = null; } // for point grades, a provided grade pass overrides the defaults in the gradeconfig. // for scales, the configuration in the gradeconfig is leading. if ($gradecfg && (isset($scale) || $this->gi->gradepass == 0)) { // if so, we need to know if the grade is . if ($finalgrade >= $gradecfg->min_completed) { return true; } else { return false; } } else if ($this->gi->gradepass > 0) { $range = floatval($this->gi->grademax - $this->gi->grademin); // if no gradeconfig and gradepass is set, use that one to determine config. if ($finalgrade >= $this->gi->gradepass) { return true; } else { return false; } } else { // Blind assumptions if nothing is provided. // over 55% of range is completed. // if range >= 3 and failed is enabled, assume that this means failed. $g = floatval($finalgrade - $this->gi->grademin); $range = floatval($this->gi->grademax - $this->gi->grademin); $score = $g / $range; if ($score > 0.55) { return true; } else { return false; } } } }