. /** * * @package local_treestudyplan * @copyright 2023 P.M. Kuipers * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace local_treestudyplan; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir.'/externallib.php'); use \grade_item; class gradingscanner { private static $modsupported = []; private static $coursestudents = []; private $scanner = null; private $gi = null; private $pendingcache = []; public static function supported($mod) { if (!array_key_exists($mod, self::$modsupported)) { self::$modsupported[$mod] = class_exists("\local_treestudyplan\\local\\ungradedscanners\\{$mod}_scanner"); } return self::$modsupported[$mod]; } public static function get_course_students($courseid) { global $CFG; if (!array_key_exists($courseid, self::$coursestudents)) { $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::$coursestudents[$courseid] = $students; } return self::$coursestudents[$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; $completedpass = 0; $completedfail = 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)) { $completedpass++; } else { $completedfail++; } } } } return [ 'ungraded' => $ungraded, 'completed' => $completed, 'completed_pass' => $completedpass, 'completed_fail' => $completedfail, '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; } } } }