2023-05-29 23:01:04 +02:00
|
|
|
<?php
|
2023-08-24 23:02:41 +02:00
|
|
|
// 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/>.
|
|
|
|
/**
|
2023-08-28 09:21:00 +02:00
|
|
|
* Scan course completion criteria for pending grading actions
|
2023-08-24 23:02:41 +02:00
|
|
|
* @package local_treestudyplan
|
|
|
|
* @copyright 2023 P.M. Kuipers
|
|
|
|
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
|
*/
|
2023-05-29 23:01:04 +02:00
|
|
|
|
|
|
|
namespace local_treestudyplan;
|
2023-08-25 12:04:27 +02:00
|
|
|
defined('MOODLE_INTERNAL') || die();
|
|
|
|
|
2023-05-29 23:01:04 +02:00
|
|
|
require_once($CFG->libdir.'/externallib.php');
|
|
|
|
|
|
|
|
use \grade_item;
|
2023-08-28 09:21:00 +02:00
|
|
|
use \completion_criteria;
|
2023-05-29 23:01:04 +02:00
|
|
|
|
2023-08-28 09:21:00 +02:00
|
|
|
/**
|
|
|
|
* Scan course completion criteria for pending grading actions
|
|
|
|
*/
|
2023-08-25 10:41:56 +02:00
|
|
|
class completionscanner {
|
2023-08-28 09:21:00 +02:00
|
|
|
/**
|
|
|
|
* Cache of supported mods
|
|
|
|
* @var array
|
|
|
|
*/
|
2023-08-25 09:33:42 +02:00
|
|
|
private static $modsupported = [];
|
2023-08-28 09:21:00 +02:00
|
|
|
/**
|
|
|
|
* Cache of enrolled students in a particular course
|
|
|
|
* @var array
|
|
|
|
*/
|
2023-08-25 09:33:42 +02:00
|
|
|
private static $coursestudents = [];
|
2023-08-28 09:21:00 +02:00
|
|
|
/** The internally used grading scanner
|
|
|
|
* @var local\ungradedscanners\scanner_base
|
|
|
|
*/
|
2023-05-29 23:01:04 +02:00
|
|
|
private $scanner = null;
|
2023-08-28 09:21:00 +02:00
|
|
|
/**
|
|
|
|
* Course module
|
|
|
|
* @var \cm_info
|
|
|
|
*/
|
2023-05-29 23:01:04 +02:00
|
|
|
private $cm = null;
|
2023-08-28 09:21:00 +02:00
|
|
|
/** @var grade_item */
|
2023-05-29 23:01:04 +02:00
|
|
|
private $gi = null;
|
2023-08-28 09:21:00 +02:00
|
|
|
/**
|
|
|
|
* Cache of pending ungraded results per user
|
|
|
|
* @var array
|
|
|
|
*/
|
2023-08-25 09:33:42 +02:00
|
|
|
private $pendingcache = [];
|
2023-05-29 23:01:04 +02:00
|
|
|
|
2023-08-28 09:21:00 +02:00
|
|
|
/**
|
|
|
|
* 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 {
|
2023-08-25 09:33:42 +02:00
|
|
|
if (!array_key_exists($mod, self::$modsupported)) {
|
|
|
|
self::$modsupported[$mod] = class_exists("\local_treestudyplan\\local\\ungradedscanners\\{$mod}_scanner");
|
2023-05-29 23:01:04 +02:00
|
|
|
}
|
2023-08-25 09:33:42 +02:00
|
|
|
return self::$modsupported[$mod];
|
2023-05-29 23:01:04 +02:00
|
|
|
}
|
|
|
|
|
2023-08-28 09:21:00 +02:00
|
|
|
/**
|
|
|
|
* List all users enrolled in a course as student by userid
|
|
|
|
* @param int $courseid Course id of the course to check
|
|
|
|
* @return int[] Array if user ids
|
|
|
|
*/
|
|
|
|
public static function get_course_students($courseid) : array {
|
2023-05-29 23:01:04 +02:00
|
|
|
global $CFG;
|
2023-08-25 09:33:42 +02:00
|
|
|
if (!array_key_exists($courseid, self::$coursestudents)) {
|
2023-05-29 23:01:04 +02:00
|
|
|
$students = [];
|
|
|
|
$context = \context_course::instance($courseid);
|
2023-08-24 23:02:41 +02:00
|
|
|
foreach (explode(', ', $CFG->gradebookroles) as $roleid) {
|
2023-05-29 23:01:04 +02:00
|
|
|
$roleid = trim($roleid);
|
|
|
|
$students = array_keys(get_role_users($roleid, $context, false, 'u.id', 'u.id ASC'));
|
|
|
|
}
|
2023-08-25 09:33:42 +02:00
|
|
|
self::$coursestudents[$courseid] = $students;
|
2023-05-29 23:01:04 +02:00
|
|
|
}
|
2023-08-25 09:33:42 +02:00
|
|
|
return self::$coursestudents[$courseid];
|
2023-05-29 23:01:04 +02:00
|
|
|
}
|
|
|
|
|
2023-08-28 09:21:00 +02:00
|
|
|
/**
|
|
|
|
* 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) {
|
2023-06-16 23:12:17 +02:00
|
|
|
$this->courseid = $course->id;
|
|
|
|
$this->course = $course;
|
|
|
|
$this->modinfo = get_fast_modinfo($course);
|
2023-05-29 23:01:04 +02:00
|
|
|
$this->crit = $crit;
|
2023-06-16 23:12:17 +02:00
|
|
|
|
|
|
|
$this->completioninfo = new \completion_info($course);
|
2023-08-24 23:02:41 +02:00
|
|
|
|
|
|
|
// Find a related scanner if the type is an activity type.
|
|
|
|
if ($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
|
|
|
|
// First find the course module.
|
2023-06-16 23:12:17 +02:00
|
|
|
$this->cm = $this->modinfo->get_cm($crit->moduleinstance);
|
2023-08-25 11:52:05 +02:00
|
|
|
$gi = grade_item::fetch( ['itemtype' => 'mod',
|
|
|
|
'itemmodule' => $this->cm->modname,
|
|
|
|
'iteminstance' => $this->cm->instance,
|
|
|
|
'courseid' => $this->courseid]);
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($gi !== false) {
|
2023-08-25 11:52:05 +02:00
|
|
|
/* 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.
|
|
|
|
*/
|
2023-08-24 23:02:41 +02:00
|
|
|
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)) {
|
2023-05-29 23:01:04 +02:00
|
|
|
$scannerclass = "\local_treestudyplan\\local\ungradedscanners\\{$gi->itemmodule}_scanner";
|
|
|
|
$this->scanner = new $scannerclass($gi);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-08-28 09:21:00 +02:00
|
|
|
/**
|
|
|
|
* 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)) {
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($this->scanner === null) {
|
2023-08-28 09:21:00 +02:00
|
|
|
$this->pendingcache[$userid] = false;
|
2023-08-24 23:09:20 +02:00
|
|
|
} else {
|
2023-08-28 09:21:00 +02:00
|
|
|
$this->pendingcache[$userid] = $this->scanner->has_ungraded_submission($userid);;
|
2023-05-29 23:01:04 +02:00
|
|
|
}
|
|
|
|
}
|
2023-08-28 09:21:00 +02:00
|
|
|
return $this->pendingcache[$userid];
|
2023-08-24 23:02:41 +02:00
|
|
|
}
|
2023-05-29 23:01:04 +02:00
|
|
|
|
2023-08-28 09:21:00 +02:00
|
|
|
/**
|
|
|
|
* Webservice structure for basic info
|
|
|
|
* @param int $value Webservice requirement constant
|
|
|
|
*/
|
|
|
|
public static function structure($value = VALUE_OPTIONAL) : \external_description {
|
2023-05-29 23:01:04 +02:00
|
|
|
return new \external_single_structure([
|
|
|
|
"ungraded" => new \external_value(PARAM_INT, 'number of ungraded submissions'),
|
2023-06-03 00:01:43 +02:00
|
|
|
"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'),
|
2023-05-29 23:01:04 +02:00
|
|
|
"students" => new \external_value(PARAM_INT, 'number of students that should submit'),
|
2023-08-24 23:02:41 +02:00
|
|
|
], "details about gradable submissions", $value);
|
2023-05-29 23:01:04 +02:00
|
|
|
}
|
|
|
|
|
2023-08-28 09:21:00 +02:00
|
|
|
/**
|
|
|
|
* Webservice model for basic info
|
|
|
|
*/
|
|
|
|
public function model() : array {
|
2023-08-24 23:02:41 +02:00
|
|
|
|
2023-08-25 09:44:34 +02:00
|
|
|
// Get completion info.
|
2023-06-16 23:12:17 +02:00
|
|
|
$students = self::get_course_students($this->courseid);
|
|
|
|
$completed = 0;
|
|
|
|
$ungraded = 0;
|
2023-08-25 09:33:42 +02:00
|
|
|
$completedpass = 0;
|
|
|
|
$completedfail = 0;
|
2023-08-24 23:02:41 +02:00
|
|
|
foreach ($students as $userid) {
|
|
|
|
if ($this->pending($userid)) {
|
|
|
|
// First check if the completion needs grading.
|
2023-06-16 23:12:17 +02:00
|
|
|
$ungraded++;
|
|
|
|
} else {
|
2023-08-24 23:02:41 +02:00
|
|
|
$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.
|
2023-08-25 09:33:42 +02:00
|
|
|
$completionstatus = $this->completioninfo->get_grade_completion($this->cm, $userid);
|
2023-08-24 23:02:41 +02:00
|
|
|
|
2023-08-25 09:33:42 +02:00
|
|
|
if ($completionstatus == COMPLETION_COMPLETE_PASS) {
|
|
|
|
$completedpass++;
|
|
|
|
} else if ($completionstatus == COMPLETION_COMPLETE_FAIL) {
|
|
|
|
$completedfail++;
|
|
|
|
} else if ($completionstatus == COMPLETION_COMPLETE) {
|
2023-06-16 23:12:17 +02:00
|
|
|
$completed++;
|
|
|
|
}
|
2023-08-25 10:41:56 +02:00
|
|
|
} else {
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($completion->is_complete()) {
|
2023-06-16 23:12:17 +02:00
|
|
|
$completed++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-29 23:01:04 +02:00
|
|
|
return [
|
2023-06-16 23:12:17 +02:00
|
|
|
'ungraded' => $ungraded,
|
|
|
|
'completed' => $completed,
|
2023-08-25 09:33:42 +02:00
|
|
|
'completed_pass' => $completedpass,
|
|
|
|
'completed_fail' => $completedfail,
|
2023-06-16 23:12:17 +02:00
|
|
|
'students' => count($students),
|
2023-05-29 23:01:04 +02:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2023-08-25 11:52:05 +02:00
|
|
|
}
|