175 lines
		
	
	
		
			No EOL
		
	
	
		
			6.5 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			175 lines
		
	
	
		
			No EOL
		
	
	
		
			6.5 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/>.
 | |
| /**
 | |
|  *
 | |
|  * @package    local_treestudyplan
 | |
|  * @copyright  2023 P.M. Kuipers
 | |
|  * @license    https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | |
|  */
 | |
| 
 | |
| namespace local_treestudyplan;
 | |
| 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;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
| } | 
