. /** * * @package local_treestudyplan * @copyright 2023 P.M. Kuipers * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace local_treestudyplan\local\aggregators; use \local_treestudyplan\courseinfo; use \local_treestudyplan\corecompletioninfo; use \local_treestudyplan\gradeinfo; use \local_treestudyplan\studyitem; use \local_treestudyplan\completion; use \local_treestudyplan\debug; class core_aggregator extends \local_treestudyplan\aggregator { public const DEPRECATED = false; private $agcfg = null; private function cfg() { if (empty($this->agcfg)) { $this->agcfg = (object)[ 'accept_pending_as_submitted' => false, // Also count ungraded but submitted . ]; } return $this->agcfg; } public function __construct($configstr) { // Allow public constructor for testing purposes. $this->initialize($configstr); } protected function initialize($configstr) { // First initialize with the defaults. foreach (["accept_pending_as_submitted"] as $key) { $this->cfg()->$key = boolval(get_config('local_treestudyplan', "bistate_{$key}")); } // Next, decode json. $config = \json_decode($configstr, true); if (is_array($config)) { // Copy all valid config settings to this item. foreach (["accept_pending_as_submitted"] as $key) { if (array_key_exists($key, $config)) { $this->cfg()->$key = boolval($config[$key]); } } } } // Return active configuration model. public function config_string() { return json_encode([ "accept_pending_as_submitted" => $this->cfg()->accept_pending_as_submitted, ]); } public function select_gradables() { return false; } public function deprecated() { return self::DEPRECATED; } public function use_required_grades() { return true; } public function use_item_conditions() { return false; } public function usecorecompletioninfo() { return true; } /** * Return course completion information based on the core completion infromation * Possible states: * completion::EXCELLENT - Completed with excellent results * completion::GOOD - Completed with good results * completion::COMPLETED - Completed * completion::PROGRESS - Started, but not completed yey * completion::FAILED - Failed * completion::INCOMPLETE - Not yet started * * @param mixed $courseinfo * @param mixed $studyitem * @param mixed $userid * @return void */ public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid) { // Retrieve the core completion info from the core. $course = $courseinfo->course(); $completion = new \completion_info($course); if ($completion->is_enabled() && $completion->is_tracked_user($userid)) { if ($completion->is_course_complete($userid)) { // Now, the trick is to determine what constitutes excellent and good completion.... // TODO: Determine excellent and maybe good completion. // Option: Use course end grade to determine that... // Probably needs a config value in the aggregator.... return completion::COMPLETED; } else { // Check if the course is over or not, if it is over, display failed. // Else, return PROGRESS. // Retrieve timing through courseinfo . $timing = courseinfo::coursetiming($course); // Not met and time is passed, means FAILED. if ($timing == "past") { return completion::FAILED; } else { // Check if any of the requirements are being met?. $completions = $completion->get_completions($userid); foreach ($completions as $c) { if ($c->is_complete()) { // If so, return progress. return completion::PROGRESS; } } return completion::INCOMPLETE; } } } else { return completion::INCOMPLETE; } } public function aggregate_junction(array $completion, studyitem $studyitem = null, $userid = 0) { // Aggregate multiple incoming states into one junction or finish. // Possible states:. // - completion::EXCELLENT - All incoming states are excellent. // - completion::GOOD - All incoming states are at least good. // - completion::COMPLETED - All incoming states are at least completed. // - completion::FAILED - All incoming states are failed. // - completion::INCOMPLETE - All incoming states are incomplete. // - completion::PROGRESS - All other states. // First count all states. $statecount = completion::count_states($completion); $total = count($completion); if ( $total == $statecount[completion::EXCELLENT]) { return completion::EXCELLENT; } else if ( $total == ( $statecount[completion::EXCELLENT] + $statecount[completion::GOOD]) ) { return completion::GOOD; } else if ( $total == ( $statecount[completion::EXCELLENT] + $statecount[completion::GOOD] + $statecount[completion::COMPLETED]) ) { return completion::COMPLETED; } else if ( $statecount[completion::FAILED]) { return completion::FAILED; } else if ( $total == $statecount[completion::INCOMPLETE]) { return completion::INCOMPLETE; } else { return completion::PROGRESS; } } // CORE COMPLETION DOESN'T REALLY USE THE FUNCTIONS BELOW. // AGGREGATORS ARE GOING TO BE DEPRECATED ANYWAY... but used in legacy parts of this plugin. public function grade_completion(gradeinfo $gradeinfo, $userid) { global $DB; $table = "local_treestudyplan_gradecfg"; $gradeitem = $gradeinfo->get_gradeitem(); $grade = $gradeitem->get_final($userid); if (empty($grade)) { return completion::INCOMPLETE; } else if ($grade->finalgrade === null) { // On assignments, grade NULL means a submission has not yet been graded,. // But on quizes this can also mean a quiz might have been started. // Therefor, we treat a NULL result as a reason to check the relevant gradingscanner for presence of pending items. // Since we want old results to be visible until a pending item was graded, we only use this state here. // Pending items are otherwise expressly indicated by the "pendingsubmission" field in the user model. if ($gradeinfo->get_gradingscanner()->pending($userid)) { return completion::PENDING; } else { return completion::INCOMPLETE; } } else { $grade = $gradeitem->get_final($userid); // First determine if we have a grade_config for this scale or this maximum grade. $finalgrade = $grade->finalgrade; $scale = $gradeinfo->get_scale(); if ( isset($scale)) { $gradecfg = $DB->get_record($table, ["scale_id" => $scale->id]); } else if ($gradeitem->grademin == 0) { $gradecfg = $DB->get_record($table, ["grade_points" => $gradeitem->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) || $gradeitem->gradepass == 0)) { // If so, we need to know if the grade is . if ($finalgrade >= $gradecfg->min_completed) { // Return completed if completed. return completion::COMPLETED; } else if ($this->use_failed && $finalgrade < $gradecfg->min_progress) { // Return failed if failed is enabled and the grade is less than the minimum grade for progress. return completion::FAILED; } else { return completion::PROGRESS; } } else if ($gradeitem->gradepass > 0) { $range = floatval($gradeitem->grademax - $gradeitem->grademin); // If no gradeconfig and gradepass is set, use that one to determine config. if ($finalgrade >= $gradeitem->gradepass) { return completion::COMPLETED; } else if ($this->use_failed && $gradeitem->gradepass >= 3 && $range >= 3 && $finalgrade == 1) { // Return failed if failed is enabled and the grade is 1, while there are at leas 3 states. return completion::FAILED; } else { return completion::PROGRESS; } } 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 - $gradeitem->grademin); $range = floatval($gradeitem->grademax - $gradeitem->grademin); $score = $g / $range; if ($score > 0.55) { return completion::COMPLETED; } else if ($this->use_failed && $range >= 3 && $finalgrade == 1) { // Return failed if failed is enabled and the grade is 1, while there are at leas 3 states. return completion::FAILED; } else { return completion::PROGRESS; } } } } }