moodle_local_treestudyplan/classes/completionscanner.php
2024-06-02 18:47:23 +02:00

213 lines
8.0 KiB
PHP

<?php
// This file is part of the Studyplan plugin for Moodle
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
/**
* 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),
];
}
}