. /** * Scan course completion criteria for pending grading actions * @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; use \completion_criteria; /** * Scan course completion criteria for pending grading actions */ class completionscanner { /** * Cache of supported mods * @var array */ private static $modsupported = []; /** The internally used grading scanner * @var local\ungradedscanners\scanner_base */ private $scanner = null; /** @var int */ private $courseid; /** @var stdClass */ private $course; /** @var \course_modinfo */ private $modinfo; /** @var completion_criteria */ private $crit; /** * Course module * @var \cm_info */ private $cm = null; /** @var grade_item */ private $gi = null; /** * Cache of pending ungraded results per user * @var array */ private $pendingcache = []; /** * Check if a certain activity type is supported for scanning pending results * @param string $mod name of activity module */ public static function supported($mod) : bool { if (!array_key_exists($mod, self::$modsupported)) { self::$modsupported[$mod] = class_exists("\local_treestudyplan\\local\\ungradedscanners\\{$mod}_scanner"); } return self::$modsupported[$mod]; } /** * Filter a list of students to return only the students enrolled as student * in this scanner's course * @param int[] $studentlist Array of student id's * @return int[] */ private function filter_studentlist(array $studentlist) : array { $coursestudents = courseinfo::get_course_students($this->courseid); return array_intersect($studentlist, $coursestudents); } /** * Construct new scanner based on completion criteria and course * @param completion_criteria $crit Criteria to check for * @param stdClass $course Course DB record */ public function __construct(completion_criteria $crit, $course) { $this->courseid = $course->id; $this->course = $course; $this->modinfo = get_fast_modinfo($course); $this->crit = $crit; $this->completioninfo = new \completion_info($course); // Find a related scanner if the type is an activity type. if ($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) { // First find the course module. $this->cm = $this->modinfo->get_cm($crit->moduleinstance); $gi = grade_item::fetch( ['itemtype' => 'mod', 'itemmodule' => $this->cm->modname, 'iteminstance' => $this->cm->instance, 'courseid' => $this->courseid]); if ($gi !== false) { /* Grade none items should not be relevant. Note that the grade status is probably only relevant if the item has not yet received a completion, but has been submitted. */ if (($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE)) { // If it's a relevant grade type, initialize a scanner if possible. $this->gi = $gi; if (self::supported($gi->itemmodule)) { $scannerclass = "\local_treestudyplan\\local\ungradedscanners\\{$gi->itemmodule}_scanner"; $this->scanner = new $scannerclass($gi); } } } } } /** * Check if the criteria this scanner scans has pending submissions for a specific user * @param int $userid ID of the user to check for */ public function pending($userid) : bool { if (!array_key_exists($userid, $this->pendingcache)) { if ($this->scanner === null) { $this->pendingcache[$userid] = false; } else { $this->pendingcache[$userid] = $this->scanner->has_ungraded_submission($userid);; } } return $this->pendingcache[$userid]; } /** * Webservice structure for basic info * @param int $value Webservice requirement constant */ public static function structure($value = VALUE_OPTIONAL) : \external_description { 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); } /** * Webservice model for basic info * @param int[]|null $studentlist Array of student userids to use for checking. Leave empty to use all course students */ public function model(array $studentlist = null) : array { /* If an array of students is provided (usually the case if called from a courecompletioninfo object), make sure to filter it out, so only the students enrolled in the course are included.. Otherwise statistics are marred. */ // Get completion info. $students = isset($studentlist) ? $this->filter_studentlist($studentlist) : courseinfo::get_course_students($this->courseid); $completed = 0; $ungraded = 0; $completedpass = 0; $completedfail = 0; foreach ($students as $userid) { $completion = $this->completioninfo->get_user_completion($userid, $this->crit); if ($this->crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) { // If it's an activity completion, add all the relevant activities as sub-items. // Retrieve data for this object. $data = $this->completioninfo->get_data($this->cm, false, $userid); // If it's an activity completion, add all the relevant activities as sub-items. $completionstatus = $data->completionstate; if ($completionstatus == COMPLETION_COMPLETE_PASS) { $completedpass++; } else if ($completionstatus == COMPLETION_COMPLETE_FAIL) { $completedfail++; } else if ($completionstatus == COMPLETION_COMPLETE) { $completed++; } else if ($this->pending($userid)) { // Check if the completion needs grading. $ungraded++; } } else { if ($completion->is_complete()) { $completed++; } } } return [ 'ungraded' => $ungraded, 'completed' => $completed, 'completed_pass' => $completedpass, 'completed_fail' => $completedfail, 'students' => count($students), ]; } }