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 based on failed, completed, excellent states for grades
|
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;
|
|
|
|
|
2024-06-02 18:47:23 +02:00
|
|
|
use local_treestudyplan\courseinfo;
|
|
|
|
use local_treestudyplan\gradeinfo;
|
|
|
|
use local_treestudyplan\studyitem;
|
|
|
|
use local_treestudyplan\completion;
|
2023-05-17 21:19:14 +02:00
|
|
|
|
2023-08-27 15:12:54 +02:00
|
|
|
/**
|
|
|
|
* Aggregate course results based on failed, achieved, completed states for grades
|
|
|
|
* @deprecated This aggregation style is no longer used, but included to support old study plans
|
|
|
|
*/
|
2023-05-17 21:19:14 +02:00
|
|
|
class tristate_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 = true;
|
2023-08-27 15:12:54 +02:00
|
|
|
/** @var string */
|
2023-05-17 21:19:14 +02:00
|
|
|
private const DEFAULT_CONDITION = "50";
|
|
|
|
|
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 true;
|
|
|
|
}
|
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
|
|
|
|
|
|
|
/**
|
2023-11-23 07:44:04 +01:00
|
|
|
* Determine if the aggregation method uses manual activity selection,
|
|
|
|
* @return bool True if the aggregation method uses manual activity selection
|
2023-08-27 15:12:54 +02:00
|
|
|
*/
|
2023-11-23 07:44:04 +01:00
|
|
|
public function use_manualactivityselection() {
|
2023-08-25 12:16:51 +02:00
|
|
|
return true;
|
|
|
|
}
|
2023-05-17 21:19:14 +02:00
|
|
|
|
2023-08-27 15:12:54 +02:00
|
|
|
/**
|
|
|
|
* Aggregate completions into final result
|
|
|
|
* @param int[] $a List of completion inputs
|
|
|
|
* @param string $condition Condition description [ALL, 67, 50, ANY]
|
|
|
|
* @return int Aggregated completion as completion class constant
|
|
|
|
*/
|
2023-05-17 21:19:14 +02:00
|
|
|
protected function aggregate_completion(array $a, $condition = "50") {
|
2023-09-02 22:07:51 +02:00
|
|
|
if (in_array(strtoupper($condition), ['ALL', '67', '50', 'ANY'])) {
|
2023-08-25 09:44:34 +02:00
|
|
|
// Condition is one of the valid conditions.
|
2023-08-25 09:33:42 +02:00
|
|
|
$ccompleted = 0;
|
|
|
|
$cexcellent = 0;
|
|
|
|
$cprogress = 0;
|
|
|
|
$cpending = 0;
|
2023-08-25 17:33:20 +02:00
|
|
|
$count = count($a);
|
2023-05-17 21:19:14 +02:00
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($count > 0) {
|
2023-05-17 21:19:14 +02:00
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
foreach ($a as $c) {
|
2023-08-25 17:33:20 +02:00
|
|
|
$cprogress += ($c >= completion::PROGRESS) ? 1 : 0;
|
|
|
|
$ccompleted += ($c >= completion::COMPLETED) ? 1 : 0;
|
|
|
|
$cexcellent += ($c >= completion::EXCELLENT) ? 1 : 0;
|
|
|
|
$cpending += ($c >= completion::PENDING) ? 1 : 0;
|
2023-05-17 21:19:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$required = [
|
|
|
|
'ALL' => 1.00 * $count,
|
|
|
|
'67' => 0.67 * $count,
|
|
|
|
'50' => 0.50 * $count,
|
|
|
|
'ANY' => 1,
|
|
|
|
][$condition];
|
|
|
|
|
2023-08-25 09:33:42 +02:00
|
|
|
if ($cexcellent >= $required) {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::EXCELLENT;
|
2023-08-25 09:33:42 +02:00
|
|
|
} else if ($ccompleted >= $required) {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::COMPLETED;
|
|
|
|
} else {
|
2023-08-25 11:52:05 +02:00
|
|
|
/* Return PROGRESS if one or more completions are COMPLETED or EXCELLENT,
|
|
|
|
but the aggregation margin is not met.
|
|
|
|
State PROGRESS will not carry on if aggregations are chained.
|
|
|
|
*/
|
2023-08-25 09:33:42 +02:00
|
|
|
if ($cprogress > 0) {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::PROGRESS;
|
2023-08-25 09:33:42 +02:00
|
|
|
} else if ($cpending > 0) {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::PENDING;
|
2023-08-24 23:09:20 +02:00
|
|
|
} else {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::INCOMPLETE;
|
|
|
|
}
|
|
|
|
}
|
2023-08-24 23:09:20 +02:00
|
|
|
} else {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::INCOMPLETE;
|
|
|
|
}
|
2023-08-25 09:33:42 +02:00
|
|
|
} else {
|
2023-08-25 13:04:19 +02:00
|
|
|
// Indeterminable.
|
2023-05-17 21:19:14 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-27 15:12:54 +02:00
|
|
|
/**
|
|
|
|
* Aggregate all completions in a course into one final course completion
|
|
|
|
* 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
|
|
|
|
*/
|
2023-08-24 23:02:41 +02:00
|
|
|
public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid) {
|
2023-11-24 23:00:53 +01:00
|
|
|
$condition = self::DEFAULT_CONDITION;
|
2024-06-02 19:23:40 +02:00
|
|
|
|
2023-05-17 21:19:14 +02:00
|
|
|
$list = [];
|
2023-08-24 23:02:41 +02:00
|
|
|
foreach (gradeinfo::list_studyitem_gradables($studyitem) as $gi) {
|
|
|
|
$list[] = $this->grade_completion($gi, $userid);
|
2023-05-17 21:19:14 +02:00
|
|
|
}
|
2023-08-24 23:02:41 +02:00
|
|
|
$completion = self::aggregate_completion($list, $condition);
|
2023-05-17 21:19:14 +02:00
|
|
|
return $completion;
|
|
|
|
}
|
|
|
|
|
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-08-24 23:02:41 +02:00
|
|
|
public function aggregate_junction(array $completion, studyitem $studyitem, $userid) {
|
|
|
|
$completed = self::aggregate_completion($completion, $studyitem->conditions());
|
2023-08-25 09:44:34 +02:00
|
|
|
// If null result (conditions are unknown/null) - default to ALL.
|
2024-06-02 23:23:32 +02:00
|
|
|
return isset($completed) ? $completed : (self::aggregate_completion($completion, 'ALL'));
|
2023-05-17 21:19:14 +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) {
|
|
|
|
global $DB;
|
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) {
|
2024-06-02 19:23:40 +02:00
|
|
|
// On assignments, grade NULL means a submission has not yet been graded, .
|
2023-08-25 09:44:34 +02:00
|
|
|
// 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:02:41 +02:00
|
|
|
|
2023-08-24 23:09:20 +02:00
|
|
|
} else {
|
2023-05-17 21:19:14 +02:00
|
|
|
$finalgrade = $grade->finalgrade;
|
2023-08-25 17:33:20 +02:00
|
|
|
$scale = $gradeinfo->get_scale();
|
2023-05-17 21:19:14 +02:00
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($gradeitem->gradepass > 0) {
|
|
|
|
// Base completion off of gradepass (if set).
|
|
|
|
if ($gradeitem->grademax > $gradeitem->gradepass && $finalgrade >= $gradeitem->grademax) {
|
|
|
|
// If gradepass is configured .
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::EXCELLENT;
|
2023-08-24 23:09:20 +02:00
|
|
|
} else if ($finalgrade >= $gradeitem->gradepass) {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::COMPLETED;
|
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:.
|
2023-08-25 09:44:34 +02:00
|
|
|
// Over 55% of range is completed.
|
|
|
|
// Over 85% of range is excellent.
|
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.85) {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::EXCELLENT;
|
2023-08-24 23:09:20 +02:00
|
|
|
} else if ($score > 0.55) {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::COMPLETED;
|
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
|
|
|
}
|