moodle_local_treestudyplan/classes/gradingscanner.php

226 lines
7.9 KiB
PHP
Raw Permalink Normal View History

<?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/>.
2024-07-19 12:31:26 +02:00
2023-08-24 23:02:41 +02:00
/**
2023-08-28 09:21:00 +02:00
* Scan gradables for a pending grading action
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
*/
namespace local_treestudyplan;
2023-08-25 12:04:27 +02:00
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/externallib.php');
2024-06-02 18:47:23 +02:00
use grade_item;
2023-08-28 09:21:00 +02:00
/**
* Scan gradables for a pending grading action
*/
2023-08-25 10:41:56 +02:00
class gradingscanner {
2023-08-28 09:21:00 +02:00
/**
* Cache of supported mods
2023-08-28 11:26:14 +02:00
* @var array
2023-08-28 09:21:00 +02:00
*/
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
*/
private $scanner = null;
2023-08-28 09:21:00 +02:00
/** @var grade_item */
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-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
*/
2024-06-02 19:23:40 +02:00
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-08-25 09:33:42 +02:00
return self::$modsupported[$mod];
}
2023-08-28 11:26:14 +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
*/
2024-06-02 19:23:40 +02:00
public static function get_course_students($courseid): array {
global $CFG;
2023-08-25 09:33:42 +02:00
if (!array_key_exists($courseid, self::$coursestudents)) {
$students = [];
$context = \context_course::instance($courseid);
2024-06-03 23:24:16 +02:00
foreach (explode(',', $CFG->gradebookroles) as $roleid) {
$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-08-25 09:33:42 +02:00
return self::$coursestudents[$courseid];
}
2023-08-28 09:21:00 +02:00
/**
* Construct new scanner based on grade item
* @param grade_item $gi Grade item
*/
2023-08-24 23:02:41 +02:00
public function __construct(grade_item $gi) {
$this->courseid = $gi->courseid;
$this->gi = $gi;
2023-08-24 23:02:41 +02:00
if (self::supported($gi->itemmodule)) {
$scannerclass = "\local_treestudyplan\\local\ungradedscanners\\{$gi->itemmodule}_scanner";
$this->scanner = new $scannerclass($gi);
}
}
2023-08-24 23:02:41 +02:00
2023-08-28 11:26:14 +02:00
/**
2023-08-28 09:21:00 +02:00
* Check if this scanner is usable (has an internal activity specific scanner)
*/
2024-06-02 19:23:40 +02:00
public function is_available(): bool {
return $this->scanner !== null;
}
2023-08-28 09:21:00 +02:00
/**
* Check if the gradable item this scanner scans has pending submissions for a specific user
* @param int $userid ID of the user to check for
*/
2024-06-02 19:23:40 +02:00
public function pending($userid): bool {
2023-08-28 09:21:00 +02:00
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-08-28 09:21:00 +02:00
return $this->pendingcache[$userid];
2023-08-24 23:02:41 +02:00
}
2023-08-28 09:21:00 +02:00
/**
* Webservice structure for basic info
* @param int $value Webservice requirement constant
*/
2024-06-02 19:23:40 +02:00
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'),
2023-08-24 23:02:41 +02:00
], "details about gradable submissions", $value);
}
2023-08-28 09:21:00 +02:00
/**
* Webservice model for basic info
*/
2024-06-02 19:23:40 +02:00
public function model(): array {
2023-08-24 23:02:41 +02:00
// Upda.
$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.
$ungraded++;
} else {
$grade = $this->gi->get_final($userid);
2024-06-02 23:23:32 +02:00
if ((!empty($grade->finalgrade)) && is_numeric($grade->finalgrade)) {
2023-08-25 12:16:51 +02:00
// Compare grade to minimum grade.
2023-08-24 23:02:41 +02:00
if ($this->grade_passed($grade)) {
2023-08-25 09:33:42 +02:00
$completedpass++;
} else {
2023-08-25 09:33:42 +02:00
$completedfail++;
}
}
}
}
return [
'ungraded' => $ungraded,
'completed' => $completed,
2023-08-25 09:33:42 +02:00
'completed_pass' => $completedpass,
'completed_fail' => $completedfail,
'students' => count($students),
];
}
2023-08-28 09:21:00 +02:00
/**
* Check if a grade is considered passed according to the rules
2023-08-28 11:26:14 +02:00
* @param grade_grade $grade
2023-08-28 09:21:00 +02:00
*/
2024-06-02 19:23:40 +02:00
private function grade_passed($grade): bool {
2023-08-28 09:21:00 +02:00
// Function copied from bistate aggregator to avoid reference mazes.
global $DB;
$table = "local_treestudyplan_gradecfg";
2023-08-25 09:44:34 +02:00
// First determine if we have a grade_config for this scale or this maximum grade.
$finalgrade = $grade->finalgrade;
$scale = $this->gi->load_scale();
2024-06-02 23:23:32 +02:00
if (isset($scale)) {
2023-08-25 10:41:56 +02:00
$gradecfg = $DB->get_record($table, ["scale_id" => $scale->id]);
2023-08-24 23:09:20 +02:00
} else if ($this->gi->grademin == 0) {
2023-08-25 10:41:56 +02:00
$gradecfg = $DB->get_record($table, ["grade_points" => $this->gi->grademax]);
2023-08-25 09:33:42 +02:00
} else {
$gradecfg = null;
}
2023-08-25 09:44:34 +02:00
// For point grades, a provided grade pass overrides the defaults in the gradeconfig.
// For scales, the configuration in the gradeconfig is leading.
2023-08-24 23:02:41 +02:00
if ($gradecfg && (isset($scale) || $this->gi->gradepass == 0)) {
2023-08-25 09:44:34 +02:00
// If so, we need to know if the grade is .
2023-08-24 23:02:41 +02:00
if ($finalgrade >= $gradecfg->min_completed) {
return true;
2023-08-24 23:09:20 +02:00
} else {
return false;
}
2023-08-24 23:09:20 +02:00
} else if ($this->gi->gradepass > 0) {
$range = floatval($this->gi->grademax - $this->gi->grademin);
2023-08-25 09:44:34 +02:00
// If no gradeconfig and gradepass is set, use that one to determine config.
2023-08-24 23:02:41 +02:00
if ($finalgrade >= $this->gi->gradepass) {
return true;
2023-08-24 23:09:20 +02:00
} else {
return false;
}
2023-08-24 23:09:20 +02:00
} else {
2023-08-24 23:02:41 +02:00
// Blind assumptions if nothing is provided.
2023-08-25 09:44:34 +02:00
// 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;
2023-08-24 23:02:41 +02:00
if ($score > 0.55) {
return true;
2023-08-24 23:09:20 +02:00
} else {
return false;
}
}
}
2023-08-25 11:52:05 +02:00
}