. /** * Aggregate course results with moodle course completion * @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; /** * Aggregate course results with moodle course completion */ class core_aggregator extends \local_treestudyplan\aggregator { /** @var bool */ public const DEPRECATED = false; /** @var array */ private $agcfg = null; /** * Retrieve or initialize current config object * @return stdClass */ private function cfg() { if (empty($this->agcfg)) { $this->agcfg = (object)[ 'accept_pending_as_submitted' => false, // Also count ungraded but submitted . ]; } return $this->agcfg; } /** * Create new instance of aggregation method * @param string $configstr Aggregation configuration string */ public function __construct($configstr) { // Allow public constructor for testing purposes. $this->initialize($configstr); } /** * Initialize the aggregation method * @param string $configstr Aggregation configuration string */ 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 the current configuration string. * @return string Configuration string */ public function config_string() { return json_encode([ "accept_pending_as_submitted" => $this->cfg()->accept_pending_as_submitted, ]); } /** * Determine if aggregation method wants to select gradables * @return bool True if aggregation method needs gradables to be selected */ public function select_gradables() { return false; } /** * Determine if aggregation method is deprecated * @return bool True if aggregation method is deprecated */ public function deprecated() { return self::DEPRECATED; } /** * Determine if Aggregation method makes use of "required grades" in a course/module. * @return bool True if Aggregation method makes use of "required grades" in a course/module. */ public function use_required_grades() { return true; } /** * Determine if aggregation method makes use of required grades * @return bool True if aggregation method makes use of */ public function use_item_conditions() { return false; } /** * Determine if the aggregation method uses core_completion, or treestudyplan custom completion. * @return bool True if the aggregation method uses core_completion */ 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 courseinfo $courseinfo Courseinfo object for the course to check * @param studyitem $studyitem Studyitem object for the course to check * @param int $userid Id of user to check this course for * @return int Aggregated completion as completion class constant */ 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; } } /** * Aggregate juncton/filter inputs into one final junction outcome * @param int[] $completion List of completion inputs * @param studyitem $studyitem Studyitem object for the junction * @param int $userid Id of user to check completion for * @return int Aggregated completion as completion class constant */ public function aggregate_junction(array $completion, studyitem $studyitem, $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. $method = strtoupper($studyitem->conditions()); // One of ANY or ALL. // First count all states. $statecount = completion::count_states($completion); $total = count($completion); if($method == "ANY"){ if ( $statecount[completion::EXCELLENT] >= 1 ) { return completion::EXCELLENT; } else if ( $statecount[completion::GOOD] >= 1 ) { return completion::GOOD; } else if ( $statecount[completion::COMPLETED] >= 1 ) { return completion::COMPLETED; } else if ( $statecount[completion::PROGRESS] >= 1 ) { return completion::PROGRESS; } else if ( $statecount[completion::FAILED] >= 1) { return completion::FAILED; } else { return completion::INCOMPLETE; } } else { /* ALL (default) */ 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; } } } /** * Determine completion for a single grade and user * @param gradeinfo $gradeinfo Gradeinfo object for grade to check * @param mixed $userid Id of user to check completion for * @return int Aggregated completion as completion class constant */ public function grade_completion(gradeinfo $gradeinfo, $userid) { // CORE COMPLETION DOESN'T REALLY USE THIS FUNCTION. 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; } } } } }