initialize($configstr); } protected function initialize($configstr) { // First initialize with the defaults foreach(["thresh_excellent", "thresh_good", "thresh_completed","thresh_progress",] as $key){ $val = intval(get_config('local_treestudyplan', "bistate_{$key}")); if($val >= 0 && $val <= 100) { $this->$key = floatval($val)/100; } } foreach(["use_failed", "accept_pending_as_submitted"] as $key){ $this->$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(["thresh_excellent", "thresh_good", "thresh_completed","thresh_progress",] as $key){ if(array_key_exists($key,$config)){ $val = $config[$key]; if($val >= 0 && $val <= 100) { $this->$key = floatval($val)/100; } } } foreach(["use_failed", "accept_pending_as_submitted"] as $key){ if(array_key_exists($key,$config)){ $this->$key = boolval($config[$key]); } } } else { debug::msg("ARRAY is NOT CONFIG",""); } } // Return active configuration model public function config_string() { return json_encode([ "thresh_excellent" => 100*$this->thresh_excellent, "thresh_good" => 100*$this->thresh_good, "thresh_completed" => 100*$this->thresh_completed, "thresh_progress" => 100*$this->thresh_progress, "use_failed" => $this->use_failed, "accept_pending_as_submitted" => $this->accept_pending_as_submitted, ]); } public function needSelectGradables(){ return True;} public function isDeprecated() { return self::DEPRECATED;} public function useRequiredGrades() { return True;} public function useItemConditions() { return False;} public function aggregate_binary_goals(array $completions, array $required = []){ // function is public to allow access for the testing code // return te following conditions // Possible states: // - completion::EXCELLENT - At least $thresh_excellent fraction of goals are complete and all required goals are met // - completion::GOOD - At least $thresh_good fraction of goals are complete and all required goals are met // - completion::COMPLETED - At least $thresh_complete fraction of goals are completed and all required goals are met // - completion::FAILED - At least $thresh_progress fraction of goals is not failed // - completion::INCOMPLETE - No goals have been started // - completion::PROGRESS - All other states $total = count($completions); $completed = 0; $progress = 0; $failed = 0; $started = 0; $total_required = 0; $required_met = 0; $MIN_PROGRESS = ($this->accept_pending_as_submitted)?completion::PENDING:completion::PROGRESS; foreach($completions as $index => $c) { $completed += ($c >= completion::COMPLETED)?1:0; $progress += ($c >= $MIN_PROGRESS)?1:0; $failed += ($c <= completion::FAILED)?1:0; } $started = $progress + $failed; $allrequiredmet = ($required_met >= $total_required); $fraction_completed = ($total >0)?(floatval($completed)/floatval($total)):0.0; $fraction_progress = ($total >0)?(floatval($progress)/floatval($total)):0.0; $fraction_failed = ($total >0)?(floatval($failed)/floatval($total)):0.0; $fraction_started = ($total >0)?(floatval($started)/floatval($total)):0.0; if($total == 0){ return completion::INCOMPLETE; } if($fraction_completed >= $this->thresh_excellent && $allrequiredmet){ return completion::EXCELLENT; } else if($fraction_completed >= $this->thresh_good && $allrequiredmet){ return completion::GOOD; } else if($fraction_completed >= $this->thresh_completed && $allrequiredmet){ return completion::COMPLETED; } else if($started == 0){ return completion::INCOMPLETE; } else if($this->use_failed && ($fraction_failed >= $this->thresh_progress)){ return completion::FAILED; } else { return completion::PROGRESS; } } public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid){ // Note: studyitem condition config is not used in this aggregator. // loop through all associated gradables and count the totals, completed, etc.. $completions = []; $required = []; foreach(gradeinfo::list_studyitem_gradables($studyitem) as $gi){ $completions[] = $this->grade_completion($gi,$userid); if($gi->is_required()){ // if it's a required grade // also add it's index in the completion list to the list of required grades $required[] = count($completions) - 1; } } // Combine the aquired completions into one return self::aggregate_binary_goals($completions,$required); } 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; } } public function grade_completion(gradeinfo $gradeinfo, $userid) { global $DB; $table = "local_treestudyplan_gradecfg"; $gradeitem = $gradeinfo->getGradeitem(); $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->getGradingscanner()->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->getScale(); 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; } } } } }