2023-05-17 21:19:14 +02:00
|
|
|
<?php
|
2023-08-24 23:02:41 +02:00
|
|
|
// 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/>.
|
|
|
|
/**
|
2023-08-27 15:12:54 +02:00
|
|
|
* Aggregate course results with moodle course completion
|
2023-08-24 23:02:41 +02:00
|
|
|
* @package local_treestudyplan
|
|
|
|
* @copyright 2023 P.M. Kuipers
|
|
|
|
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
|
*/
|
|
|
|
|
2023-05-17 21:19:14 +02:00
|
|
|
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;
|
|
|
|
|
2023-08-27 15:12:54 +02:00
|
|
|
/**
|
|
|
|
* Aggregate course results with moodle course completion
|
|
|
|
*/
|
2023-05-17 21:19:14 +02:00
|
|
|
class core_aggregator extends \local_treestudyplan\aggregator {
|
2023-08-27 15:12:54 +02:00
|
|
|
/** @var bool */
|
2023-05-17 21:19:14 +02:00
|
|
|
public const DEPRECATED = false;
|
2023-08-27 15:12:54 +02:00
|
|
|
/** @var array */
|
2023-08-25 17:33:20 +02:00
|
|
|
private $agcfg = null;
|
|
|
|
|
2023-08-27 15:12:54 +02:00
|
|
|
/**
|
|
|
|
* Retrieve or initialize current config object
|
|
|
|
* @return stdClass
|
|
|
|
*/
|
2023-08-25 17:33:20 +02:00
|
|
|
private function cfg() {
|
|
|
|
if (empty($this->agcfg)) {
|
|
|
|
$this->agcfg = (object)[
|
|
|
|
'accept_pending_as_submitted' => false, // Also count ungraded but submitted .
|
|
|
|
];
|
|
|
|
}
|
|
|
|
return $this->agcfg;
|
|
|
|
}
|
2023-05-17 21:19:14 +02:00
|
|
|
|
2023-08-27 15:12:54 +02:00
|
|
|
/**
|
|
|
|
* Create new instance of aggregation method
|
|
|
|
* @param string $configstr Aggregation configuration string
|
|
|
|
*/
|
2023-05-17 21:19:14 +02:00
|
|
|
public function __construct($configstr) {
|
2023-08-25 09:44:34 +02:00
|
|
|
// Allow public constructor for testing purposes.
|
2023-05-17 21:19:14 +02:00
|
|
|
$this->initialize($configstr);
|
|
|
|
}
|
|
|
|
|
2023-08-27 15:12:54 +02:00
|
|
|
/**
|
|
|
|
* Initialize the aggregation method
|
|
|
|
* @param string $configstr Aggregation configuration string
|
|
|
|
*/
|
2023-05-17 21:19:14 +02:00
|
|
|
protected function initialize($configstr) {
|
2023-08-24 23:02:41 +02:00
|
|
|
// First initialize with the defaults.
|
|
|
|
foreach (["accept_pending_as_submitted"] as $key) {
|
2023-08-25 17:33:20 +02:00
|
|
|
$this->cfg()->$key = boolval(get_config('local_treestudyplan', "bistate_{$key}"));
|
2023-05-17 21:19:14 +02:00
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
// Next, decode json.
|
|
|
|
$config = \json_decode($configstr, true);
|
2023-05-17 21:19:14 +02:00
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
if (is_array($config)) {
|
2023-08-25 09:44:34 +02:00
|
|
|
// Copy all valid config settings to this item.
|
2023-08-24 23:02:41 +02:00
|
|
|
foreach (["accept_pending_as_submitted"] as $key) {
|
|
|
|
if (array_key_exists($key, $config)) {
|
2023-08-25 17:33:20 +02:00
|
|
|
$this->cfg()->$key = boolval($config[$key]);
|
2023-05-17 21:19:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-27 15:12:54 +02:00
|
|
|
/**
|
|
|
|
* Return the current configuration string.
|
|
|
|
* @return string Configuration string
|
2023-08-27 22:20:17 +02:00
|
|
|
*/
|
2023-05-17 21:19:14 +02:00
|
|
|
public function config_string() {
|
|
|
|
return json_encode([
|
2023-08-25 17:33:20 +02:00
|
|
|
"accept_pending_as_submitted" => $this->cfg()->accept_pending_as_submitted,
|
2023-05-17 21:19:14 +02:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2023-08-27 15:12:54 +02:00
|
|
|
/**
|
|
|
|
* Determine if aggregation method wants to select gradables
|
|
|
|
* @return bool True if aggregation method needs gradables to be selected
|
|
|
|
*/
|
2023-08-25 13:34:31 +02:00
|
|
|
public function select_gradables() {
|
2023-08-25 12:16:51 +02:00
|
|
|
return false;
|
|
|
|
}
|
2023-08-27 15:12:54 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Determine if aggregation method is deprecated
|
|
|
|
* @return bool True if aggregation method is deprecated
|
|
|
|
*/
|
2023-08-25 13:34:31 +02:00
|
|
|
public function deprecated() {
|
2023-08-25 12:16:51 +02:00
|
|
|
return self::DEPRECATED;
|
|
|
|
}
|
2023-08-27 15:12:54 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2023-08-25 13:34:31 +02:00
|
|
|
public function use_required_grades() {
|
2023-08-25 12:16:51 +02:00
|
|
|
return true;
|
|
|
|
}
|
2023-08-27 15:12:54 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Determine if aggregation method makes use of required grades
|
2023-08-27 22:20:17 +02:00
|
|
|
* @return bool True if aggregation method makes use of
|
2023-08-27 15:12:54 +02:00
|
|
|
*/
|
2023-08-25 13:34:31 +02:00
|
|
|
public function use_item_conditions() {
|
2023-08-25 12:16:51 +02:00
|
|
|
return false;
|
|
|
|
}
|
2023-08-27 15:12:54 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Determine if the aggregation method uses core_completion, or treestudyplan custom completion.
|
|
|
|
* @return bool True if the aggregation method uses core_completion
|
|
|
|
*/
|
2023-08-25 12:16:51 +02:00
|
|
|
public function usecorecompletioninfo() {
|
|
|
|
return true;
|
|
|
|
}
|
2023-05-17 21:19:14 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
2023-08-27 15:12:54 +02:00
|
|
|
* @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
|
2023-05-17 21:19:14 +02:00
|
|
|
*/
|
2023-08-24 23:02:41 +02:00
|
|
|
public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid) {
|
2023-08-25 17:33:20 +02:00
|
|
|
// Retrieve the core completion info from the core.
|
|
|
|
$course = $courseinfo->course();
|
|
|
|
$completion = new \completion_info($course);
|
2023-05-17 21:19:14 +02:00
|
|
|
|
2023-08-25 17:33:20 +02:00
|
|
|
if ($completion->is_enabled() && $completion->is_tracked_user($userid)) {
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($completion->is_course_complete($userid)) {
|
2023-05-17 21:19:14 +02:00
|
|
|
// Now, the trick is to determine what constitutes excellent and good completion....
|
2023-08-24 23:02:41 +02:00
|
|
|
// TODO: Determine excellent and maybe good completion.
|
2023-05-17 21:19:14 +02:00
|
|
|
// Option: Use course end grade to determine that...
|
|
|
|
// Probably needs a config value in the aggregator....
|
|
|
|
|
|
|
|
return completion::COMPLETED;
|
|
|
|
} else {
|
2023-08-24 23:02:41 +02:00
|
|
|
// Check if the course is over or not, if it is over, display failed.
|
|
|
|
// Else, return PROGRESS.
|
2023-05-17 21:19:14 +02:00
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
// Retrieve timing through courseinfo .
|
2023-05-17 21:19:14 +02:00
|
|
|
$timing = courseinfo::coursetiming($course);
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
// Not met and time is passed, means FAILED.
|
|
|
|
if ($timing == "past") {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::FAILED;
|
2023-08-24 23:09:20 +02:00
|
|
|
} else {
|
2023-08-24 23:02:41 +02:00
|
|
|
// Check if any of the requirements are being met?.
|
2023-05-17 21:19:14 +02:00
|
|
|
$completions = $completion->get_completions($userid);
|
2023-08-24 23:02:41 +02:00
|
|
|
foreach ($completions as $c) {
|
|
|
|
if ($c->is_complete()) {
|
|
|
|
// If so, return progress.
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::PROGRESS;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return completion::INCOMPLETE;
|
|
|
|
}
|
|
|
|
}
|
2023-08-25 10:41:56 +02:00
|
|
|
} else {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::INCOMPLETE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-27 15:12:54 +02:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2023-09-02 22:07:51 +02:00
|
|
|
public function aggregate_junction(array $completion, studyitem $studyitem, $userid = 0) {
|
2023-05-17 21:19:14 +02:00
|
|
|
// Aggregate multiple incoming states into one junction or finish.
|
2023-08-24 23:02:41 +02:00
|
|
|
// 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.
|
|
|
|
|
2023-09-08 12:47:29 +02:00
|
|
|
$method = strtoupper($studyitem->conditions()); // One of ANY or ALL.
|
2023-09-02 22:07:51 +02:00
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
// First count all states.
|
2023-05-17 21:19:14 +02:00
|
|
|
$statecount = completion::count_states($completion);
|
|
|
|
$total = count($completion);
|
|
|
|
|
2023-09-08 12:47:29 +02:00
|
|
|
if ($method == "ANY") {
|
2023-09-02 22:07:51 +02:00
|
|
|
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;
|
|
|
|
}
|
2023-09-08 12:47:29 +02:00
|
|
|
} else { /* default value of ALL */
|
2023-09-02 22:07:51 +02:00
|
|
|
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;
|
|
|
|
}
|
2023-05-17 21:19:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-27 22:20:17 +02:00
|
|
|
|
2023-08-27 15:12:54 +02:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2023-05-17 21:19:14 +02:00
|
|
|
public function grade_completion(gradeinfo $gradeinfo, $userid) {
|
2023-08-27 22:20:17 +02:00
|
|
|
// CORE COMPLETION DOESN'T REALLY USE THIS FUNCTION.
|
|
|
|
|
2023-05-17 21:19:14 +02:00
|
|
|
global $DB;
|
|
|
|
$table = "local_treestudyplan_gradecfg";
|
2023-08-25 17:33:20 +02:00
|
|
|
$gradeitem = $gradeinfo->get_gradeitem();
|
2023-05-17 21:19:14 +02:00
|
|
|
$grade = $gradeitem->get_final($userid);
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
if (empty($grade)) {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::INCOMPLETE;
|
2023-08-25 12:16:51 +02:00
|
|
|
} else if ($grade->finalgrade === null) {
|
2023-08-25 09:44:34 +02:00
|
|
|
// 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.
|
2023-08-24 23:02:41 +02:00
|
|
|
// Therefor, we treat a NULL result as a reason to check the relevant gradingscanner for presence of pending items.
|
2023-05-17 21:19:14 +02:00
|
|
|
|
|
|
|
// Since we want old results to be visible until a pending item was graded, we only use this state here.
|
2023-08-24 23:02:41 +02:00
|
|
|
// Pending items are otherwise expressly indicated by the "pendingsubmission" field in the user model.
|
2023-08-25 17:33:20 +02:00
|
|
|
if ($gradeinfo->get_gradingscanner()->pending($userid)) {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::PENDING;
|
|
|
|
} else {
|
|
|
|
return completion::INCOMPLETE;
|
|
|
|
}
|
2023-08-24 23:09:20 +02:00
|
|
|
} else {
|
2023-06-16 23:12:17 +02:00
|
|
|
$grade = $gradeitem->get_final($userid);
|
2023-08-25 09:44:34 +02:00
|
|
|
// First determine if we have a grade_config for this scale or this maximum grade.
|
2023-05-17 21:19:14 +02:00
|
|
|
$finalgrade = $grade->finalgrade;
|
2023-08-25 17:33:20 +02:00
|
|
|
$scale = $gradeinfo->get_scale();
|
2023-08-24 23:02:41 +02:00
|
|
|
if ( isset($scale)) {
|
2023-08-25 10:41:56 +02:00
|
|
|
$gradecfg = $DB->get_record($table, ["scale_id" => $scale->id]);
|
2023-08-24 23:09:20 +02:00
|
|
|
} else if ($gradeitem->grademin == 0) {
|
2023-08-25 10:41:56 +02:00
|
|
|
$gradecfg = $DB->get_record($table, ["grade_points" => $gradeitem->grademax]);
|
2023-08-25 09:33:42 +02:00
|
|
|
} else {
|
2023-05-17 21:19:14 +02:00
|
|
|
$gradecfg = null;
|
|
|
|
}
|
|
|
|
|
2023-08-25 09:44:34 +02:00
|
|
|
// For point grades, a provided grade pass overrides the defaults in the gradeconfig.
|
|
|
|
// For scales, the configuration in the gradeconfig is leading.
|
2023-06-16 23:12:17 +02:00
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($gradecfg && (isset($scale) || $gradeitem->gradepass == 0)) {
|
2023-08-25 09:44:34 +02:00
|
|
|
// If so, we need to know if the grade is .
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($finalgrade >= $gradecfg->min_completed) {
|
2023-08-25 09:44:34 +02:00
|
|
|
// Return completed if completed.
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::COMPLETED;
|
2023-08-24 23:09:20 +02:00
|
|
|
} else if ($this->use_failed && $finalgrade < $gradecfg->min_progress) {
|
2023-08-25 09:44:34 +02:00
|
|
|
// Return failed if failed is enabled and the grade is less than the minimum grade for progress.
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::FAILED;
|
2023-08-24 23:09:20 +02:00
|
|
|
} else {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::PROGRESS;
|
|
|
|
}
|
2023-08-24 23:09:20 +02:00
|
|
|
} else if ($gradeitem->gradepass > 0) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$range = floatval($gradeitem->grademax - $gradeitem->grademin);
|
2023-08-25 09:44:34 +02:00
|
|
|
// If no gradeconfig and gradepass is set, use that one to determine config.
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($finalgrade >= $gradeitem->gradepass) {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::COMPLETED;
|
2023-08-24 23:09:20 +02:00
|
|
|
} else if ($this->use_failed && $gradeitem->gradepass >= 3 && $range >= 3 && $finalgrade == 1) {
|
2023-08-25 09:44:34 +02:00
|
|
|
// Return failed if failed is enabled and the grade is 1, while there are at leas 3 states.
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::FAILED;
|
2023-08-24 23:09:20 +02:00
|
|
|
} else {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::PROGRESS;
|
|
|
|
}
|
2023-08-24 23:09:20 +02:00
|
|
|
} else {
|
2023-08-24 23:02:41 +02:00
|
|
|
// Blind assumptions if nothing is provided.
|
2023-08-25 09:44:34 +02:00
|
|
|
// Over 55% of range is completed.
|
|
|
|
// If range >= 3 and failed is enabled, assume that this means failed.
|
2023-05-17 21:19:14 +02:00
|
|
|
$g = floatval($finalgrade - $gradeitem->grademin);
|
|
|
|
$range = floatval($gradeitem->grademax - $gradeitem->grademin);
|
|
|
|
$score = $g / $range;
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($score > 0.55) {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::COMPLETED;
|
2023-08-24 23:09:20 +02:00
|
|
|
} else if ($this->use_failed && $range >= 3 && $finalgrade == 1) {
|
2023-08-25 09:44:34 +02:00
|
|
|
// Return failed if failed is enabled and the grade is 1, while there are at leas 3 states.
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::FAILED;
|
2023-08-24 23:09:20 +02:00
|
|
|
} else {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::PROGRESS;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-25 11:52:05 +02:00
|
|
|
}
|