. /** * 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 core_competency\api as c_api; use \local_treestudyplan\courseinfo; use \local_treestudyplan\gradeinfo; use \local_treestudyplan\studyitem; use \local_treestudyplan\completion; use local_treestudyplan\coursecompetencyinfo; /** * Aggregate course results with moodle course completion */ class competency_aggregator extends \local_treestudyplan\aggregator { /** @var bool */ public const DEPRECATED = false; /** @var stdClass */ private $agcfg = null; /** * Retrieve or initialize current config object * @return stdClass */ private function cfg() { if (empty($this->agcfg)) { $this->agcfg = (object)[ 'thresh_completed' => 0.66, // Minimum fraction that must be completed to aggregate as completed. 'use_failed' => true, // Support failed completion yes/no. ]; } 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 (["thresh_completed", ] as $key) { $val = intval(get_config('local_treestudyplan', "competency_{$key}")); if ($val >= 0 && $val <= 100) { $this->cfg()->$key = floatval($val) / 100; } } foreach (["use_failed", ] as $key) { $this->cfg()->$key = boolval(get_config('local_treestudyplan', "competency_{$key}")); } // Next, decode json. $config = \json_decode($configstr, true); if (is_array($config)) { // Copy all valid config settings to this item. foreach (["thresh_completed", ] as $key) { if (array_key_exists($key, $config)) { $val = $config[$key]; if ($val >= 0 && $val <= 100) { $this->cfg()->$key = floatval($val) / 100; } } } foreach (["use_failed",] 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([ "thresh_completed" => 100 * $this->cfg()->thresh_completed, "use_failed" => $this->cfg()->use_failed, ]); } /** * 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 course competencies, * @return bool True if the aggregation method uses course competencies, */ public function use_coursecompetencies() { 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 course competencies $course = $courseinfo->course(); $cci = new coursecompetencyinfo($course,$studyitem); $competencies = $cci->course_competencies(); $count = 0; $courseproficient = 0; $proficient = 0; foreach ($competencies as $c) { $count += 1; $p = $cci->proficiency($c,$userid); if ($p->proficient) { $proficient += 1; } if ($p->courseproficient) { $courseproficient += 1; } } // Determine minimum for $limit = $this->cfg()->thresh_completed * $count; $coursefinished = ($course->enddate) ? ($course->enddate < time()) : false; if ($courseproficient >= $count) { if ($limit < $count) { return completion::EXCELLENT; } else { return completion::COMPLETED; } } else if ($courseproficient > $limit) { return completion::COMPLETED; } else if ($courseproficient > 0) { if ( $this->cfg()->use_failed && $coursefinished) { return completion::FAILED; } else { return completion::PROGRESS; } } else { if ( $this->cfg()->use_failed && $coursefinished) { return completion::FAILED; } 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 { /* default value of ALL */ 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) { // COURSE COMPETENCIES DOESN'T REALLY USE THIS FUNCTION. } }