2023-11-23 07:44:04 +01:00
|
|
|
<?php
|
|
|
|
// 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/>.
|
|
|
|
/**
|
|
|
|
* Class to collect course completion info for a given course
|
|
|
|
* @package local_treestudyplan
|
|
|
|
* @copyright 2023 P.M. Kuipers
|
|
|
|
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
|
*/
|
|
|
|
|
|
|
|
namespace local_treestudyplan;
|
|
|
|
defined('MOODLE_INTERNAL') || die();
|
|
|
|
|
|
|
|
require_once($CFG->libdir.'/externallib.php');
|
|
|
|
require_once($CFG->libdir.'/gradelib.php');
|
|
|
|
require_once($CFG->dirroot.'/course/lib.php');
|
|
|
|
|
|
|
|
use core_competency\course_competency;
|
2023-12-11 23:52:00 +01:00
|
|
|
use core_competency\user_competency_course;
|
2023-11-23 07:44:04 +01:00
|
|
|
use core_competency\competency;
|
|
|
|
use core_competency\api as c_api;
|
2023-11-24 23:00:53 +01:00
|
|
|
use core_competency\competency_rule_points;
|
2023-11-26 22:58:26 +01:00
|
|
|
use core_competency\evidence;
|
2023-11-27 23:11:17 +01:00
|
|
|
use core_competency\user_competency;
|
2023-11-23 07:44:04 +01:00
|
|
|
use stdClass;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Class to collect course competencty info for a given course
|
|
|
|
*/
|
|
|
|
class coursecompetencyinfo {
|
|
|
|
/** @var \stdClass */
|
|
|
|
private $course;
|
|
|
|
/** @var \course_modinfo */
|
|
|
|
private $modinfo;
|
|
|
|
/** @var studyitem */
|
|
|
|
private $studyitem;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Course id of relevant course
|
|
|
|
*/
|
|
|
|
public function id() {
|
|
|
|
return $this->course->id;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Construct new object for a given course
|
|
|
|
* @param \stdClass $course Course database record
|
2024-06-03 04:00:46 +02:00
|
|
|
* @param studyitem $studyitem Studyitem the course is linked in
|
2023-11-23 07:44:04 +01:00
|
|
|
*/
|
|
|
|
public function __construct($course, $studyitem) {
|
|
|
|
global $DB;
|
|
|
|
$this->course = $course;
|
|
|
|
$this->studyitem = $studyitem;
|
|
|
|
$this->completion = new \completion_info($this->course);
|
|
|
|
$this->modinfo = get_fast_modinfo($this->course);
|
|
|
|
}
|
|
|
|
|
2023-11-27 23:11:17 +01:00
|
|
|
/**
|
|
|
|
* Webservice structure for completion stats
|
|
|
|
* @param int $value Webservice requirement constant
|
|
|
|
*/
|
2024-06-02 19:23:40 +02:00
|
|
|
public static function completionstats_structure($value = VALUE_OPTIONAL): \external_description {
|
2023-11-27 23:11:17 +01:00
|
|
|
return new \external_single_structure([
|
|
|
|
"ungraded" => new \external_value(PARAM_INT, 'number of ungraded submissions'),
|
|
|
|
"completed" => new \external_value(PARAM_INT, 'number of completed students'),
|
|
|
|
"students" => new \external_value(PARAM_INT, 'number of students that should submit'),
|
|
|
|
"completed_pass" => new \external_value(PARAM_INT, 'number of completed-pass students'),
|
|
|
|
"completed_fail" => new \external_value(PARAM_INT, 'number of completed-fail students'),
|
|
|
|
], "details about gradable submissions", $value);
|
|
|
|
}
|
|
|
|
|
2024-06-02 23:23:32 +02:00
|
|
|
/**
|
|
|
|
* Convert completion stats to web service model.
|
|
|
|
* @param object $stats Stats object
|
|
|
|
*/
|
2024-06-02 19:23:40 +02:00
|
|
|
protected static function completionstats($stats) {
|
2023-11-27 23:11:17 +01:00
|
|
|
return [
|
|
|
|
"students" => $stats->count,
|
2024-06-02 23:23:32 +02:00
|
|
|
"completed" => 0,
|
2023-11-27 23:11:17 +01:00
|
|
|
"ungraded" => $stats->nneedreview,
|
|
|
|
"completed_pass" => $stats->nproficient,
|
|
|
|
"completed_fail" => $stats->nfailed,
|
|
|
|
];
|
|
|
|
}
|
2023-11-23 07:44:04 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Generic competency info structure for individual competency stats
|
2024-06-03 04:00:46 +02:00
|
|
|
* @param bool $recurse True if child competencies may be included
|
2023-11-23 07:44:04 +01:00
|
|
|
*/
|
2024-06-02 19:23:40 +02:00
|
|
|
public static function competencyinfo_structure($recurse=true): \external_description {
|
2023-11-23 07:44:04 +01:00
|
|
|
$struct = [
|
|
|
|
"id" => new \external_value(PARAM_INT, 'competency id'),
|
2023-11-24 23:00:53 +01:00
|
|
|
"title" => new \external_value(PARAM_RAW, 'competency display title'),
|
|
|
|
"details" => new \external_value(PARAM_RAW, 'competency details'),
|
2023-11-23 07:44:04 +01:00
|
|
|
"description" => new \external_value(PARAM_RAW, 'competency description'),
|
2023-11-24 23:00:53 +01:00
|
|
|
"ruleoutcome" => new \external_value(PARAM_TEXT, 'competency rule outcome text', VALUE_OPTIONAL),
|
|
|
|
"rule" => new \external_value(PARAM_RAW, 'competency rule description', VALUE_OPTIONAL),
|
2023-11-23 07:44:04 +01:00
|
|
|
"path" => new \external_multiple_structure(new \external_single_structure([
|
|
|
|
"id" => new \external_value(PARAM_INT),
|
2023-11-24 23:00:53 +01:00
|
|
|
"title" => new \external_value(PARAM_RAW),
|
|
|
|
"type" => new \external_value(PARAM_TEXT),
|
2023-11-23 07:44:04 +01:00
|
|
|
]), 'competency path'),
|
2024-06-02 19:23:40 +02:00
|
|
|
'ucid' => new \external_value(PARAM_INT, 'user competencyid', VALUE_OPTIONAL),
|
2023-11-23 07:44:04 +01:00
|
|
|
"grade" => new \external_value(PARAM_TEXT, 'competency grade', VALUE_OPTIONAL),
|
|
|
|
"coursegrade" => new \external_value(PARAM_TEXT, 'course competency grade', VALUE_OPTIONAL),
|
2024-06-02 19:23:40 +02:00
|
|
|
"proficient" => new \external_value(PARAM_BOOL, 'competency proficiency', VALUE_OPTIONAL),
|
|
|
|
"courseproficient" => new \external_value(PARAM_BOOL, 'course competency proficiency', VALUE_OPTIONAL),
|
|
|
|
"needreview" => new \external_value(PARAM_BOOL, 'waiting for review or review in progress', VALUE_OPTIONAL),
|
2023-11-27 23:11:17 +01:00
|
|
|
"completionstats" => static::completionstats_structure(VALUE_OPTIONAL),
|
2024-06-02 19:23:40 +02:00
|
|
|
"required" => new \external_value(PARAM_BOOL, 'if required in parent competency rule', VALUE_OPTIONAL),
|
|
|
|
"points" => new \external_value(PARAM_INT, 'number of points in parent competency rule', VALUE_OPTIONAL),
|
|
|
|
"progress" => new \external_value(PARAM_INT, 'number completed child competencies/points', VALUE_OPTIONAL),
|
|
|
|
"count" => new \external_value(PARAM_INT, 'number of child competencies/points required', VALUE_OPTIONAL),
|
|
|
|
"feedback" => new \external_value(PARAM_RAW, 'feedback provided with this competency', VALUE_OPTIONAL),
|
2023-11-23 07:44:04 +01:00
|
|
|
];
|
2024-06-02 19:23:40 +02:00
|
|
|
if ($recurse) {
|
2024-06-02 23:23:32 +02:00
|
|
|
$struct["children"] = new \external_multiple_structure(
|
|
|
|
self::competencyinfo_structure(false), 'child competencies', VALUE_OPTIONAL);
|
2023-11-23 07:44:04 +01:00
|
|
|
}
|
|
|
|
return new \external_single_structure($struct, 'course completion info');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Webservice structure for editor info
|
|
|
|
* @param int $value Webservice requirement constant
|
|
|
|
*/
|
2024-06-02 19:23:40 +02:00
|
|
|
public static function editor_structure($value = VALUE_REQUIRED): \external_description {
|
2023-11-23 07:44:04 +01:00
|
|
|
return new \external_single_structure([
|
|
|
|
"competencies" => new \external_multiple_structure(self::competencyinfo_structure(), 'competencies'),
|
2024-06-02 19:23:40 +02:00
|
|
|
"proficient" => new \external_value(PARAM_INT, 'number of proficient user/competencys ', VALUE_OPTIONAL),
|
|
|
|
"total" => new \external_value(PARAM_INT, 'total number of gradable user/competencies', VALUE_OPTIONAL),
|
2023-11-23 07:44:04 +01:00
|
|
|
], 'course completion info', $value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Webservice structure for userinfo
|
|
|
|
* @param int $value Webservice requirement constant
|
|
|
|
*/
|
2024-06-02 19:23:40 +02:00
|
|
|
public static function user_structure($value = VALUE_REQUIRED): \external_description {
|
2023-11-23 07:44:04 +01:00
|
|
|
return new \external_single_structure([
|
|
|
|
"competencies" => new \external_multiple_structure(self::competencyinfo_structure(), 'competencies'),
|
2023-11-26 22:58:26 +01:00
|
|
|
"progress" => new \external_value(PARAM_INT, 'number completed competencies'),
|
2024-06-02 19:23:40 +02:00
|
|
|
"count" => new \external_value(PARAM_INT, 'number of competencies', VALUE_OPTIONAL),
|
2023-11-23 07:44:04 +01:00
|
|
|
], 'course completion info', $value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create basic competency information model from competency
|
2024-06-03 04:00:46 +02:00
|
|
|
* @param object $competency The competency to model
|
|
|
|
* @param int|null $userid Optional userid to include completion data for
|
2023-11-23 07:44:04 +01:00
|
|
|
*/
|
2024-06-02 19:23:40 +02:00
|
|
|
private function competencyinfo_model($competency, $userid=null): array {
|
|
|
|
$displayfield = get_config("local_treestudyplan", "competency_displayname");
|
|
|
|
$detailfield = get_config("local_treestudyplan", "competency_detailfield");
|
2024-06-02 23:23:32 +02:00
|
|
|
$headingfield = ($displayfield != 'description') ? $displayfield : "shortname";
|
2023-11-24 23:00:53 +01:00
|
|
|
$framework = $competency->get_framework();
|
|
|
|
|
|
|
|
$heading = $framework->get($headingfield);
|
2024-06-02 19:23:40 +02:00
|
|
|
if (empty(trim($heading))) {
|
2024-06-02 23:23:32 +02:00
|
|
|
$heading = $framework->get('shortname'); // Fall back to shortname if heading field is empty.
|
2023-11-24 23:00:53 +01:00
|
|
|
}
|
|
|
|
$path = [[
|
|
|
|
'id' => $framework->get('id'),
|
|
|
|
'title' => $heading,
|
|
|
|
'contextid' => $framework->get('contextid'),
|
|
|
|
'type' => 'framework',
|
|
|
|
]];
|
2023-11-23 07:44:04 +01:00
|
|
|
foreach ($competency->get_ancestors() as $c) {
|
|
|
|
$competencypath[] = $c->get('shortname');
|
2023-11-24 23:00:53 +01:00
|
|
|
$heading = $c->get($headingfield);
|
2024-06-02 19:23:40 +02:00
|
|
|
if (empty(trim($heading))) {
|
2024-06-02 23:23:32 +02:00
|
|
|
$heading = $c->get('shortname'); // Fall back to shortname if heading field is empty.
|
2023-11-24 23:00:53 +01:00
|
|
|
}
|
2023-11-23 07:44:04 +01:00
|
|
|
$path[] = [
|
|
|
|
'id' => $c->get('id'),
|
2023-11-24 23:00:53 +01:00
|
|
|
'title' => $heading,
|
|
|
|
'contextid' => $framework->get('contextid'),
|
|
|
|
'type' => 'competency',
|
2023-11-23 07:44:04 +01:00
|
|
|
];
|
|
|
|
}
|
2024-06-02 19:23:40 +02:00
|
|
|
|
2023-11-24 23:00:53 +01:00
|
|
|
$heading = $competency->get($headingfield);
|
2024-06-02 19:23:40 +02:00
|
|
|
if (empty(trim($heading))) {
|
2024-06-02 23:23:32 +02:00
|
|
|
$heading = $competency->get('shortname'); // Fall back to shortname if heading field is empty.
|
2023-11-24 23:00:53 +01:00
|
|
|
}
|
2023-11-23 07:44:04 +01:00
|
|
|
$path[] = [
|
|
|
|
'id' => $competency->get('id'),
|
2023-11-24 23:00:53 +01:00
|
|
|
'title' => $heading,
|
|
|
|
'contextid' => $framework->get('contextid'),
|
|
|
|
'type' => 'competency',
|
2023-11-23 07:44:04 +01:00
|
|
|
];
|
|
|
|
|
2023-11-24 23:00:53 +01:00
|
|
|
$title = $competency->get($displayfield);
|
2024-06-02 19:23:40 +02:00
|
|
|
if (empty(trim($title))) {
|
2024-06-02 23:23:32 +02:00
|
|
|
$title = $competency->get('shortname'); // Fall back to shortname if heading field is empty.
|
2023-11-24 23:00:53 +01:00
|
|
|
}
|
2023-11-23 07:44:04 +01:00
|
|
|
$model = [
|
|
|
|
'id' => $competency->get('id'),
|
2023-11-24 23:00:53 +01:00
|
|
|
'title' => $title,
|
|
|
|
'details' => $competency->get($detailfield),
|
2023-11-23 07:44:04 +01:00
|
|
|
'description' => $competency->get('description'),
|
|
|
|
'path' => $path,
|
|
|
|
];
|
2023-12-13 00:13:07 +01:00
|
|
|
if ($userid) {
|
|
|
|
$model['ucid'] = self::get_user_competency($userid, $competency->get('id'))->get('id');
|
|
|
|
}
|
2023-11-23 07:44:04 +01:00
|
|
|
|
|
|
|
return $model;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Webservice model for editor info
|
|
|
|
* @param int[] $studentlist List of user id's to use for checking issueing progress within a study plan
|
|
|
|
* @return array Webservice data model
|
|
|
|
*/
|
|
|
|
public function editor_model(array $studentlist = null) {
|
|
|
|
$coursecompetencies = $this->course_competencies();
|
|
|
|
// Next create the data model, and check user proficiency for each competency.
|
|
|
|
|
|
|
|
$count = 0;
|
|
|
|
$nproficient = 0;
|
2024-06-02 19:23:40 +02:00
|
|
|
|
2024-01-26 12:26:01 +01:00
|
|
|
$cis = [];
|
2024-06-02 23:23:32 +02:00
|
|
|
foreach ($coursecompetencies as $c) {
|
2023-11-26 22:58:26 +01:00
|
|
|
$ci = $this->competencyinfo_model($c);
|
2024-06-02 19:23:40 +02:00
|
|
|
if (!empty($studentlist)) {
|
|
|
|
$stats = $this->proficiency_stats($c, $studentlist);
|
2023-11-24 23:00:53 +01:00
|
|
|
$count += $stats->count;
|
|
|
|
$nproficient += $stats->nproficient;
|
2023-11-26 22:58:26 +01:00
|
|
|
// Copy proficiency stats to model.
|
|
|
|
foreach ((array)$stats as $key => $value) {
|
|
|
|
$ci[$key] = $value;
|
|
|
|
}
|
2023-11-27 23:11:17 +01:00
|
|
|
$ci['completionstats'] = self::completionstats($stats);
|
|
|
|
|
2023-11-23 07:44:04 +01:00
|
|
|
}
|
2023-11-24 23:00:53 +01:00
|
|
|
$ci['required'] = $this->is_required($c);
|
2023-11-23 07:44:04 +01:00
|
|
|
|
2023-11-24 23:00:53 +01:00
|
|
|
$rule = $c->get_rule_object();
|
|
|
|
$ruleoutcome = $c->get('ruleoutcome');
|
2024-06-02 19:23:40 +02:00
|
|
|
if ($rule && $ruleoutcome != competency::OUTCOME_NONE) {
|
2023-11-24 23:00:53 +01:00
|
|
|
$ruletext = $rule->get_name();
|
|
|
|
$ruleconfig = $c->get('ruleconfig');
|
2024-06-02 19:23:40 +02:00
|
|
|
|
2023-11-24 23:00:53 +01:00
|
|
|
if ($ruleoutcome == competency::OUTCOME_EVIDENCE) {
|
|
|
|
$outcometag = "evidence";
|
|
|
|
} else if ($ruleoutcome == competency::OUTCOME_COMPLETE) {
|
|
|
|
$outcometag = "complete";
|
|
|
|
} else if ($ruleoutcome == competency::OUTCOME_RECOMMEND) {
|
|
|
|
$outcometag = "recommend";
|
|
|
|
} else {
|
|
|
|
$outcometag = "none";
|
2023-11-23 07:44:04 +01:00
|
|
|
}
|
2024-06-02 19:23:40 +02:00
|
|
|
$ci["ruleoutcome"] = get_string("coursemodulecompetencyoutcome_{$outcometag}", "core_competency");
|
|
|
|
|
2023-11-24 23:00:53 +01:00
|
|
|
if ($rule instanceof competency_rule_points) {
|
|
|
|
$ruleconfig = json_decode($ruleconfig);
|
|
|
|
$points = $ruleconfig->base->points;
|
2024-06-02 19:23:40 +02:00
|
|
|
|
2024-06-02 23:23:32 +02:00
|
|
|
// Make a nice map of the competency rule config.
|
2023-11-24 23:00:53 +01:00
|
|
|
$crlist = [];
|
2024-06-02 23:23:32 +02:00
|
|
|
foreach ($ruleconfig->competencies as $cr) {
|
2023-11-24 23:00:53 +01:00
|
|
|
$crlist[$cr->id] = $cr;
|
|
|
|
}
|
2024-06-02 19:23:40 +02:00
|
|
|
$ci["rule"] = $ruletext . " ({$points} ".get_string("points", "core_grades").")";
|
2023-11-24 23:00:53 +01:00
|
|
|
} else {
|
2023-11-26 22:58:26 +01:00
|
|
|
$ci["rule"] = $ruletext;
|
2023-11-24 23:00:53 +01:00
|
|
|
}
|
2023-11-23 07:44:04 +01:00
|
|
|
|
2024-06-02 23:23:32 +02:00
|
|
|
// Get one level of children.
|
2023-11-24 23:00:53 +01:00
|
|
|
$dids = competency::get_descendants_ids($c);
|
2024-06-02 19:23:40 +02:00
|
|
|
if (count($dids) > 0) {
|
2023-11-24 23:00:53 +01:00
|
|
|
$children = [];
|
2024-06-02 23:23:32 +02:00
|
|
|
foreach ($dids as $did) {
|
2023-11-24 23:00:53 +01:00
|
|
|
$cc = new competency($did);
|
|
|
|
$cci = $this->competencyinfo_model($cc);
|
2024-06-02 19:23:40 +02:00
|
|
|
if (!empty($studentlist)) {
|
|
|
|
$stats = $this->proficiency_stats($cc, $studentlist);
|
2023-11-27 23:11:17 +01:00
|
|
|
$cci['completionstats'] = self::completionstats($stats);
|
|
|
|
}
|
2024-06-02 19:23:40 +02:00
|
|
|
if ($rule instanceof competency_rule_points) {
|
|
|
|
if (array_key_exists($did, $crlist)) {
|
2023-11-24 23:00:53 +01:00
|
|
|
$cr = $crlist[$did];
|
|
|
|
$cci["points"] = (int) $cr->points;
|
|
|
|
$cci["required"] = (int) $cr->required;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$children[] = $cci;
|
|
|
|
}
|
2024-06-02 19:23:40 +02:00
|
|
|
|
2023-11-24 23:00:53 +01:00
|
|
|
$ci["children"] = $children;
|
|
|
|
}
|
|
|
|
}
|
2023-11-23 07:44:04 +01:00
|
|
|
$cis[] = $ci;
|
|
|
|
}
|
|
|
|
$info = [
|
|
|
|
"competencies" => $cis,
|
|
|
|
];
|
2024-06-02 19:23:40 +02:00
|
|
|
if (!empty($studentlist)) {
|
2023-11-27 23:11:17 +01:00
|
|
|
$info["proficient"] = $nproficient;
|
|
|
|
$info["total"] = $count;
|
2023-11-24 23:00:53 +01:00
|
|
|
}
|
2023-11-23 07:44:04 +01:00
|
|
|
return $info;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Webservice model for user course completion info
|
|
|
|
* @param int $userid ID of user to check specific info for
|
|
|
|
* @return array Webservice data model
|
|
|
|
*/
|
|
|
|
public function user_model($userid) {
|
|
|
|
$competencies = $this->course_competencies();
|
|
|
|
$progress = 0;
|
|
|
|
|
|
|
|
$cis = [];
|
|
|
|
foreach ($competencies as $c) {
|
2024-06-02 19:23:40 +02:00
|
|
|
$ci = $this->competencyinfo_model($c, $userid);
|
2023-11-23 07:44:04 +01:00
|
|
|
// Add user info if $userid is set.
|
2024-06-02 19:23:40 +02:00
|
|
|
$p = $this->proficiency($c, $userid);
|
2023-11-23 07:44:04 +01:00
|
|
|
// Copy proficiency info to model.
|
|
|
|
foreach ((array)$p as $key => $value) {
|
|
|
|
$ci[$key] = $value;
|
|
|
|
}
|
2023-11-26 22:58:26 +01:00
|
|
|
$ci['required'] = $this->is_required($c);
|
2023-11-23 07:44:04 +01:00
|
|
|
if ($p->proficient || $p->courseproficient) {
|
|
|
|
$progress += 1;
|
|
|
|
}
|
|
|
|
|
2023-11-26 22:58:26 +01:00
|
|
|
// Retrieve feedback.
|
2024-06-02 19:23:40 +02:00
|
|
|
$ci["feedback"] = $this->retrievefeedback($c, $userid);
|
2023-11-26 22:58:26 +01:00
|
|
|
|
2023-11-24 23:00:53 +01:00
|
|
|
$rule = $c->get_rule_object();
|
|
|
|
$ruleoutcome = $c->get('ruleoutcome');
|
2024-06-02 19:23:40 +02:00
|
|
|
if ($rule && $ruleoutcome != competency::OUTCOME_NONE) {
|
2023-11-24 23:00:53 +01:00
|
|
|
$ruletext = $rule->get_name();
|
|
|
|
$ruleconfig = $c->get('ruleconfig');
|
2024-06-02 19:23:40 +02:00
|
|
|
|
2023-11-24 23:00:53 +01:00
|
|
|
if ($ruleoutcome == competency::OUTCOME_EVIDENCE) {
|
|
|
|
$outcometag = "evidence";
|
|
|
|
} else if ($ruleoutcome == competency::OUTCOME_COMPLETE) {
|
|
|
|
$outcometag = "complete";
|
|
|
|
} else if ($ruleoutcome == competency::OUTCOME_RECOMMEND) {
|
|
|
|
$outcometag = "recommend";
|
|
|
|
} else {
|
|
|
|
$outcometag = "none";
|
2023-11-23 07:44:04 +01:00
|
|
|
}
|
2024-06-02 19:23:40 +02:00
|
|
|
$ci["ruleoutcome"] = get_string("coursemodulecompetencyoutcome_{$outcometag}", "core_competency");
|
|
|
|
|
2023-11-24 23:00:53 +01:00
|
|
|
if ($rule instanceof competency_rule_points) {
|
|
|
|
$ruleconfig = json_decode($ruleconfig);
|
|
|
|
$pointsreq = $ruleconfig->base->points;
|
|
|
|
$points = 0;
|
2024-06-02 23:23:32 +02:00
|
|
|
// Make a nice map of the competency rule config.
|
2023-11-24 23:00:53 +01:00
|
|
|
$crlist = [];
|
2024-06-02 23:23:32 +02:00
|
|
|
foreach ($ruleconfig->competencies as $cr) {
|
2023-11-24 23:00:53 +01:00
|
|
|
$crlist[$cr->id] = $cr;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2023-11-23 07:44:04 +01:00
|
|
|
|
2024-06-02 23:23:32 +02:00
|
|
|
// Get one level of children.
|
2023-11-24 23:00:53 +01:00
|
|
|
$dids = competency::get_descendants_ids($c);
|
2024-06-02 19:23:40 +02:00
|
|
|
if (count($dids) > 0) {
|
2023-11-26 22:58:26 +01:00
|
|
|
$dcount = 0;
|
|
|
|
$dprogress = 0;
|
2023-11-24 23:00:53 +01:00
|
|
|
$children = [];
|
2024-06-02 23:23:32 +02:00
|
|
|
foreach ($dids as $did) {
|
2023-11-24 23:00:53 +01:00
|
|
|
$cc = new competency($did);
|
2024-06-02 19:23:40 +02:00
|
|
|
$cci = $this->competencyinfo_model($cc, $userid);
|
|
|
|
$cp = $p = $this->proficiency($cc, $userid);
|
2023-11-26 22:58:26 +01:00
|
|
|
// Copy proficiency info to model.
|
|
|
|
foreach ((array)$cp as $key => $value) {
|
|
|
|
$cci[$key] = $value;
|
|
|
|
}
|
|
|
|
// Retrieve feedback.
|
2024-06-02 19:23:40 +02:00
|
|
|
$cci["feedback"] = $this->retrievefeedback($cc, $userid);
|
2023-11-26 22:58:26 +01:00
|
|
|
|
2024-06-02 19:23:40 +02:00
|
|
|
if ($rule instanceof competency_rule_points) {
|
|
|
|
if (array_key_exists($did, $crlist)) {
|
2023-11-24 23:00:53 +01:00
|
|
|
$cr = $crlist[$did];
|
|
|
|
$cci["points"] = (int) $cr->points;
|
|
|
|
$cci["required"] = (int) $cr->required;
|
2024-06-02 19:23:40 +02:00
|
|
|
if ($cp->proficient) {
|
2023-11-24 23:00:53 +01:00
|
|
|
$points += (int) $cr->points;
|
|
|
|
}
|
|
|
|
}
|
2023-11-26 22:58:26 +01:00
|
|
|
} else {
|
|
|
|
$dcount += 1;
|
|
|
|
if ($cp->proficient) {
|
|
|
|
$dprogress += 1;
|
|
|
|
}
|
2023-11-24 23:00:53 +01:00
|
|
|
}
|
|
|
|
$children[] = $cci;
|
|
|
|
}
|
2024-06-02 19:23:40 +02:00
|
|
|
|
2023-11-24 23:00:53 +01:00
|
|
|
$ci["children"] = $children;
|
2023-11-26 22:58:26 +01:00
|
|
|
|
2023-11-24 23:00:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($rule instanceof competency_rule_points) {
|
2024-06-02 19:23:40 +02:00
|
|
|
$ci["rule"] = $ruletext . " ({$points} / {$pointsreq} ".get_string("points", "core_grades").")";
|
2023-11-26 22:58:26 +01:00
|
|
|
$ci["count"] = $pointsreq;
|
|
|
|
$ci["progress"] = $points;
|
2023-11-24 23:00:53 +01:00
|
|
|
} else {
|
2023-11-26 22:58:26 +01:00
|
|
|
$ci["rule"] = $ruletext;
|
|
|
|
$ci["count"] = $dcount;
|
|
|
|
$ci["progress"] = $dprogress;
|
2023-11-24 23:00:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2023-11-23 07:44:04 +01:00
|
|
|
$cis[] = $ci;
|
|
|
|
}
|
|
|
|
|
|
|
|
$info = [
|
|
|
|
'progress' => $progress,
|
|
|
|
"count" => count($competencies),
|
|
|
|
"competencies" => $cis,
|
|
|
|
];
|
|
|
|
|
|
|
|
return $info;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the course's competencies with user status
|
|
|
|
* @return array of Competencies Webservice data model
|
|
|
|
*/
|
|
|
|
public function course_competencies() {
|
|
|
|
$list = [];
|
2023-12-11 23:52:00 +01:00
|
|
|
|
|
|
|
$coursecompetencies = course_competency::list_course_competencies($this->course->id);
|
|
|
|
$competencies = course_competency::list_competencies($this->course->id);
|
|
|
|
|
|
|
|
foreach ($coursecompetencies as $key => $coursecompetency) {
|
|
|
|
$list[] = $competencies[$coursecompetency->get('competencyid')];
|
2023-11-23 07:44:04 +01:00
|
|
|
}
|
2023-12-11 23:52:00 +01:00
|
|
|
|
2023-11-23 07:44:04 +01:00
|
|
|
return $list;
|
|
|
|
}
|
|
|
|
|
2024-06-02 23:23:32 +02:00
|
|
|
/**
|
|
|
|
* Determine proficiency stats
|
|
|
|
* @param object $competency
|
|
|
|
* @param array $studentlist
|
|
|
|
*/
|
2024-06-02 19:23:40 +02:00
|
|
|
protected function proficiency_stats($competency, $studentlist) {
|
2023-11-23 07:44:04 +01:00
|
|
|
$r = new \stdClass();
|
|
|
|
$r->count = 0;
|
|
|
|
$r->nproficient = 0;
|
|
|
|
$r->ncourseproficient = 0;
|
2023-11-27 23:11:17 +01:00
|
|
|
$r->nneedreview = 0;
|
|
|
|
$r->nfailed = 0;
|
2023-11-23 07:44:04 +01:00
|
|
|
|
|
|
|
foreach ($studentlist as $sid) {
|
2024-06-02 19:23:40 +02:00
|
|
|
$p = $this->proficiency($competency, $sid);
|
2023-11-23 07:44:04 +01:00
|
|
|
$r->count += 1;
|
2024-06-02 23:23:32 +02:00
|
|
|
$r->nproficient += ($p->proficient === true) ? 1 : 0;
|
|
|
|
$r->nfailed += ($p->proficient === false) ? 1 : 0;
|
2024-06-02 19:23:40 +02:00
|
|
|
$r->ncourseproficient += ($p->courseproficient) ? 1 : 0;
|
|
|
|
$r->nneedreview += ($p->needreview) ? 1 : 0;
|
2023-11-23 07:44:04 +01:00
|
|
|
}
|
|
|
|
return $r;
|
|
|
|
}
|
|
|
|
|
2023-12-11 23:52:00 +01:00
|
|
|
/**
|
|
|
|
* Get a user competency. (Copied from competency api and stripped out permission checks)
|
|
|
|
*
|
|
|
|
* @param int $userid The user ID.
|
|
|
|
* @param int $competencyid The competency ID.
|
|
|
|
* @return user_competency
|
|
|
|
*/
|
|
|
|
public static function get_user_competency($userid, $competencyid) {
|
|
|
|
c_api::require_enabled();
|
2024-06-02 19:23:40 +02:00
|
|
|
$existing = user_competency::get_multiple($userid, [$competencyid]);
|
2023-12-11 23:52:00 +01:00
|
|
|
$uc = array_pop($existing);
|
|
|
|
|
|
|
|
if (!$uc) {
|
|
|
|
$uc = user_competency::create_relation($userid, $competencyid);
|
|
|
|
$uc->create();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $uc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a user competency in a course. (Copied from competency api and stripped out permission checks)
|
|
|
|
*
|
|
|
|
* @param int $courseid The id of the course to check.
|
|
|
|
* @param int $userid The id of the course to check.
|
|
|
|
* @param int $competencyid The id of the competency.
|
|
|
|
* @return user_competency_course
|
|
|
|
*/
|
|
|
|
public static function get_user_competency_in_course($courseid, $userid, $competencyid) {
|
|
|
|
c_api::require_enabled();
|
|
|
|
// First we do a permissions check.
|
|
|
|
$context = \context_course::instance($courseid);
|
|
|
|
|
|
|
|
// This will throw an exception if the competency does not belong to the course.
|
|
|
|
$competency = course_competency::get_competency($courseid, $competencyid);
|
|
|
|
|
2024-06-02 19:23:40 +02:00
|
|
|
$params = ['courseid' => $courseid, 'userid' => $userid, 'competencyid' => $competencyid];
|
2023-12-11 23:52:00 +01:00
|
|
|
$exists = user_competency_course::get_record($params);
|
|
|
|
// Create missing.
|
|
|
|
if ($exists) {
|
|
|
|
$ucc = $exists;
|
|
|
|
} else {
|
|
|
|
$ucc = user_competency_course::create_relation($userid, $competency->get('id'), $courseid);
|
|
|
|
$ucc->create();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $ucc;
|
|
|
|
}
|
|
|
|
|
2023-11-23 07:44:04 +01:00
|
|
|
/**
|
|
|
|
* Retrieve course proficiency and overall proficiency for a competency and user
|
|
|
|
*
|
|
|
|
* @param \core_competency\competency $competency
|
|
|
|
* @param int $userid
|
2024-06-02 19:23:40 +02:00
|
|
|
*
|
|
|
|
* @return stdClass
|
|
|
|
*
|
2023-11-23 07:44:04 +01:00
|
|
|
*/
|
|
|
|
public function proficiency($competency, $userid) {
|
|
|
|
$scale = $competency->get_scale();
|
|
|
|
$competencyid = $competency->get('id');
|
|
|
|
$r = new \stdClass();
|
2023-11-26 22:58:26 +01:00
|
|
|
|
2023-12-11 23:52:00 +01:00
|
|
|
$uc = self::get_user_competency($userid, $competencyid);
|
2023-11-27 23:11:17 +01:00
|
|
|
$proficiency = $uc->get('proficiency');
|
|
|
|
$r->proficient = $proficiency;
|
2023-11-23 07:44:04 +01:00
|
|
|
$r->grade = $scale->get_nearest_item($uc->get('grade'));
|
2023-11-27 23:11:17 +01:00
|
|
|
$r->needreview = (!($r->proficient) && ($uc->get('status') > user_competency::STATUS_IDLE));
|
|
|
|
$r->failed = $proficiency === false;
|
2023-11-26 22:58:26 +01:00
|
|
|
try {
|
|
|
|
// Only add course grade and proficiency if the competency is included in the course.
|
2024-06-02 19:23:40 +02:00
|
|
|
$ucc = self::get_user_competency_in_course($this->course->id, $userid, $competencyid);
|
2023-11-26 22:58:26 +01:00
|
|
|
$r->courseproficient = $ucc->get('proficiency');
|
|
|
|
$r->coursegrade = $scale->get_nearest_item($ucc->get('grade'));
|
2024-06-02 23:23:32 +02:00
|
|
|
} catch (\Exception $x) {
|
|
|
|
$ucc = null;
|
|
|
|
}
|
2023-11-23 07:44:04 +01:00
|
|
|
return $r;
|
|
|
|
}
|
2023-11-24 23:00:53 +01:00
|
|
|
|
2023-11-26 22:58:26 +01:00
|
|
|
/**
|
|
|
|
* Retrieve course proficiency and overall proficiency for a competency and user
|
|
|
|
*
|
|
|
|
* @param \core_competency\competency $competency
|
|
|
|
* @param int $userid
|
2024-06-02 19:23:40 +02:00
|
|
|
*
|
|
|
|
* @return stdClass
|
|
|
|
*
|
2023-11-26 22:58:26 +01:00
|
|
|
*/
|
|
|
|
public function retrievefeedback($competency, $userid) {
|
|
|
|
$competencyid = $competency->get('id');
|
2023-12-11 23:52:00 +01:00
|
|
|
$uc = self::get_user_competency($userid, $competencyid);
|
2023-11-26 22:58:26 +01:00
|
|
|
|
2024-06-02 23:23:32 +02:00
|
|
|
// Get evidences and sort by creation date (newest first).
|
2024-06-02 19:23:40 +02:00
|
|
|
$evidence = evidence::get_records_for_usercompetency($uc->get('id'), \context_system::instance(), 'timecreated', "DESC");
|
2023-11-26 22:58:26 +01:00
|
|
|
|
2024-06-02 23:23:32 +02:00
|
|
|
// Get the first valid note and return it.
|
|
|
|
foreach ($evidence as $e) {
|
|
|
|
if (in_array($e->get('action'), [evidence::ACTION_OVERRIDE, evidence::ACTION_COMPLETE])) {
|
2023-11-26 22:58:26 +01:00
|
|
|
return $e->get('note');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2023-11-24 23:00:53 +01:00
|
|
|
/**
|
|
|
|
* Webservice executor to mark competency as required
|
|
|
|
* @param int $competencyid ID of the competency
|
|
|
|
* @param int $itemid ID of the study item
|
|
|
|
* @param bool $required Mark competency as required or not
|
|
|
|
* @return success Always returns successful success object
|
|
|
|
*/
|
|
|
|
public static function require_competency(int $competencyid, int $itemid, bool $required) {
|
|
|
|
global $DB;
|
|
|
|
$item = studyitem::find_by_id($itemid);
|
2024-06-02 23:23:32 +02:00
|
|
|
// Make sure conditions are properly configured.
|
2023-11-24 23:00:53 +01:00
|
|
|
$conditions = [];
|
|
|
|
try {
|
2024-06-02 19:23:40 +02:00
|
|
|
$conditions = json_decode($item->conditions(), true);
|
2024-06-02 23:23:32 +02:00
|
|
|
} catch (\Exception $x) {
|
|
|
|
$conditions = [];
|
|
|
|
}
|
2023-11-24 23:00:53 +01:00
|
|
|
|
2024-06-02 23:23:32 +02:00
|
|
|
// Make sure the competencied field exists.
|
2023-11-24 23:00:53 +01:00
|
|
|
if (!isset($conditions["competencies"]) || !is_array($conditions["competencies"])) {
|
|
|
|
$conditions["competencies"] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure a record exits.
|
2024-06-02 19:23:40 +02:00
|
|
|
if (!array_key_exists($competencyid, $conditions["competencies"])) {
|
2023-11-24 23:00:53 +01:00
|
|
|
$conditions["competencies"][$competencyid] = [
|
|
|
|
"required" => boolval($required),
|
|
|
|
];
|
|
|
|
} else {
|
|
|
|
$conditions["competencies"][$competencyid]["required"] = boolval($required);
|
|
|
|
}
|
|
|
|
|
2024-06-02 23:23:32 +02:00
|
|
|
// Store conditions.
|
2023-11-24 23:00:53 +01:00
|
|
|
$item->edit(["conditions" => json_encode($conditions)]);
|
|
|
|
return success::success();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-06-03 04:00:46 +02:00
|
|
|
* Check if this competency is marked required in the studyitem
|
|
|
|
* @param object $competency The competency to check
|
2023-11-24 23:00:53 +01:00
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function is_required($competency) {
|
|
|
|
if ($this->studyitem) {
|
|
|
|
$conditions = [];
|
|
|
|
try {
|
2024-06-02 19:23:40 +02:00
|
|
|
$conditions = json_decode($this->studyitem->conditions(), true);
|
2024-06-02 23:23:32 +02:00
|
|
|
} catch (\Exception $x) {
|
|
|
|
$conditions = [];
|
|
|
|
}
|
2024-06-02 19:23:40 +02:00
|
|
|
|
2024-06-02 23:23:32 +02:00
|
|
|
// Make sure the competencied field exists.
|
|
|
|
if (isset($conditions["competencies"])
|
2023-11-24 23:00:53 +01:00
|
|
|
&& is_array($conditions["competencies"])
|
|
|
|
&& isset($conditions["competencies"][$competency->get("id")])
|
|
|
|
&& isset($conditions["competencies"][$competency->get("id")]["required"])
|
|
|
|
) {
|
|
|
|
return boolval($conditions["competencies"][$competency->get("id")]["required"]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return(false);
|
|
|
|
}
|
|
|
|
|
2023-11-23 07:44:04 +01:00
|
|
|
}
|