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/>.
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @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;
|
2023-08-25 12:04:27 +02:00
|
|
|
defined('MOODLE_INTERNAL') || die();
|
|
|
|
|
2023-05-17 21:19:14 +02:00
|
|
|
require_once($CFG->libdir.'/externallib.php');
|
|
|
|
|
|
|
|
class studyitem {
|
|
|
|
|
|
|
|
public const COMPETENCY = 'competency';
|
|
|
|
public const COURSE = 'course';
|
|
|
|
public const JUNCTION = 'junction';
|
|
|
|
public const BADGE = 'badge';
|
|
|
|
public const FINISH = 'finish';
|
|
|
|
public const START = 'start';
|
|
|
|
public const INVALID = 'invalid';
|
|
|
|
|
|
|
|
public const TABLE = "local_treestudyplan_item";
|
|
|
|
|
2023-08-25 09:33:42 +02:00
|
|
|
private static $STUDYITEMCACHE = [];
|
2023-08-24 23:02:41 +02:00
|
|
|
private $r; // Holds database record.
|
2023-05-17 21:19:14 +02:00
|
|
|
private $id;
|
2023-08-24 23:02:41 +02:00
|
|
|
|
2023-05-17 21:19:14 +02:00
|
|
|
private $courseinfo = null;
|
|
|
|
private $studyline;
|
|
|
|
private $aggregator;
|
|
|
|
|
|
|
|
public function context(): \context {
|
|
|
|
return $this->studyline->context();
|
|
|
|
}
|
|
|
|
|
2023-07-23 16:25:08 +02:00
|
|
|
public function studyline(): studyline {
|
2023-05-17 21:19:14 +02:00
|
|
|
return $this->studyline;
|
|
|
|
}
|
|
|
|
|
2023-07-23 16:25:08 +02:00
|
|
|
public function conditions() {
|
2023-05-17 21:19:14 +02:00
|
|
|
return $this->r->conditions;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function findById($id): self {
|
2023-08-25 09:33:42 +02:00
|
|
|
if (!array_key_exists($id, self::$STUDYITEMCACHE)) {
|
|
|
|
self::$STUDYITEMCACHE[$id] = new self($id);
|
2023-08-24 23:02:41 +02:00
|
|
|
}
|
2023-08-25 09:33:42 +02:00
|
|
|
return self::$STUDYITEMCACHE[$id];
|
2023-05-17 21:19:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function __construct($id) {
|
|
|
|
global $DB;
|
|
|
|
$this->id = $id;
|
2023-08-24 23:02:41 +02:00
|
|
|
$this->r = $DB->get_record(self::TABLE, ['id' => $id], "*", MUST_EXIST);
|
2023-05-17 21:19:14 +02:00
|
|
|
|
|
|
|
$this->studyline = studyline::findById($this->r->line_id);
|
2023-07-23 16:25:08 +02:00
|
|
|
$this->aggregator = $this->studyline()->studyplan()->aggregator();
|
2023-05-17 21:19:14 +02:00
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
public function id() {
|
2023-05-17 21:19:14 +02:00
|
|
|
return $this->id;
|
|
|
|
}
|
2023-08-24 23:02:41 +02:00
|
|
|
|
|
|
|
public function slot() {
|
2023-05-17 21:19:14 +02:00
|
|
|
return $this->r->slot;
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
public function layer() {
|
2023-05-17 21:19:14 +02:00
|
|
|
return $this->r->layer;
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
public function type() {
|
2023-05-17 21:19:14 +02:00
|
|
|
return $this->r->type;
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
public function courseid() {
|
2023-05-17 21:19:14 +02:00
|
|
|
return $this->r->course_id;
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
public static function exists($id) {
|
2023-05-17 21:19:14 +02:00
|
|
|
global $DB;
|
|
|
|
return is_numeric($id) && $DB->record_exists(self::TABLE, array('id' => $id));
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
public static function editor_structure($value=VALUE_REQUIRED) {
|
2023-05-17 21:19:14 +02:00
|
|
|
return new \external_single_structure([
|
|
|
|
"id" => new \external_value(PARAM_INT, 'id of study item'),
|
|
|
|
"type" => new \external_value(PARAM_TEXT, 'shortname of study item'),
|
2023-08-25 10:41:56 +02:00
|
|
|
"conditions" => new \external_value(PARAM_TEXT, 'conditions for completion'),
|
2023-05-17 21:19:14 +02:00
|
|
|
"slot" => new \external_value(PARAM_INT, 'slot in the study plan'),
|
|
|
|
"layer" => new \external_value(PARAM_INT, 'layer in the slot'),
|
2023-08-04 11:54:16 +02:00
|
|
|
"span" => new \external_value(PARAM_INT, 'how many periods the item spans'),
|
2023-05-17 21:19:14 +02:00
|
|
|
"course" => courseinfo::editor_structure(VALUE_OPTIONAL),
|
|
|
|
"badge" => badgeinfo::editor_structure(VALUE_OPTIONAL),
|
|
|
|
"continuation_id" => new \external_value(PARAM_INT, 'id of continued item'),
|
|
|
|
"connections" => new \external_single_structure([
|
|
|
|
'in' => new \external_multiple_structure(studyitemconnection::structure()),
|
|
|
|
'out' => new \external_multiple_structure(studyitemconnection::structure()),
|
|
|
|
]),
|
|
|
|
]);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
public function editor_model() {
|
2023-05-17 21:19:14 +02:00
|
|
|
return $this->generate_model("editor");
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
private function generate_model($mode) {
|
|
|
|
// Mode parameter is used to geep this function for both editor model and export model.
|
|
|
|
// (Export model results in fewer parameters on children, but is otherwise basically the same as this function).
|
2023-05-17 21:19:14 +02:00
|
|
|
global $DB;
|
|
|
|
|
|
|
|
$model = [
|
2023-08-24 23:02:41 +02:00
|
|
|
'id' => $this->r->id, // Id is needed in export model because of link references.
|
2023-08-25 10:41:56 +02:00
|
|
|
'type' => $this->isValid() ? $this->r->type : self::INVALID,
|
2023-05-17 21:19:14 +02:00
|
|
|
'conditions' => $this->r->conditions,
|
|
|
|
'slot' => $this->r->slot,
|
|
|
|
'layer' => $this->r->layer,
|
2023-08-04 11:54:16 +02:00
|
|
|
'span' => $this->r->span,
|
2023-05-17 21:19:14 +02:00
|
|
|
'continuation_id' => $this->r->continuation_id,
|
|
|
|
'connections' => [
|
|
|
|
"in" => [],
|
|
|
|
"out" => [],
|
|
|
|
]
|
|
|
|
];
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($mode == "export") {
|
2023-08-25 09:44:34 +02:00
|
|
|
// Remove slot and layer.
|
2023-05-17 21:19:14 +02:00
|
|
|
unset($model["slot"]);
|
|
|
|
unset($model["layer"]);
|
|
|
|
unset($model["continuation_id"]);
|
2023-08-24 23:02:41 +02:00
|
|
|
$model["connections"] = []; // In export mode, connections is just an array of outgoing connections.
|
|
|
|
if (!isset($this->r->conditions)) {
|
2023-05-17 21:19:14 +02:00
|
|
|
unset($model["conditions"]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
// Add course link if available.
|
2023-05-17 21:19:14 +02:00
|
|
|
$ci = $this->getcourseinfo();
|
2023-08-24 23:02:41 +02:00
|
|
|
if (isset($ci)) {
|
|
|
|
if ($mode == "export") {
|
2023-05-17 21:19:14 +02:00
|
|
|
$model['course'] = $ci->shortname();
|
|
|
|
} else {
|
2023-08-24 23:02:41 +02:00
|
|
|
$model['course'] = $ci->editor_model($this, $this->aggregator->usecorecompletioninfo());
|
2023-05-17 21:19:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
// Add badge info if available.
|
|
|
|
if (is_numeric($this->r->badge_id) && $DB->record_exists('badge', array('id' => $this->r->badge_id))) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$badge = new \core_badges\badge($this->r->badge_id);
|
|
|
|
$badgeinfo = new badgeinfo($badge);
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($mode == "export") {
|
2023-05-17 21:19:14 +02:00
|
|
|
$model['badge'] = $badgeinfo->name();
|
2023-06-16 23:12:17 +02:00
|
|
|
} else {
|
2023-08-24 23:02:41 +02:00
|
|
|
// Also supply a list of linked users, so the badgeinfo can give stats on .
|
2023-08-25 09:44:34 +02:00
|
|
|
// The amount issued, related to this studyplan.
|
2023-07-23 16:25:08 +02:00
|
|
|
$studentids = $this->studyline()->studyplan()->find_linked_userids();
|
2023-06-16 23:12:17 +02:00
|
|
|
$model['badge'] = $badgeinfo->editor_model($studentids);
|
2023-05-17 21:19:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($mode == "export") {
|
|
|
|
// Also export gradables.
|
2023-05-17 21:19:14 +02:00
|
|
|
$gradables = gradeinfo::list_studyitem_gradables($this);
|
2023-08-24 23:02:41 +02:00
|
|
|
if (count($gradables) > 0) {
|
|
|
|
$model["gradables"] = [];
|
|
|
|
foreach ($gradables as $g) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$model["gradables"][] = $g->export_model();;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
// Add incoming and outgoing connection info.
|
2023-08-25 09:33:42 +02:00
|
|
|
$connout = studyitemconnection::find_outgoing($this->id);
|
2023-05-17 21:19:14 +02:00
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($mode == "export") {
|
2023-08-25 09:33:42 +02:00
|
|
|
foreach ($connout as $c) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$model["connections"][] = $c->to_id();
|
|
|
|
}
|
2023-08-24 23:09:20 +02:00
|
|
|
} else {
|
2023-08-25 09:33:42 +02:00
|
|
|
foreach ($connout as $c) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$model['connections']['out'][$c->to_id()] = $c->model();
|
|
|
|
}
|
2023-08-25 09:33:42 +02:00
|
|
|
$connin = studyitemconnection::find_incoming($this->id);
|
|
|
|
foreach ($connin as $c) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$model['connections']['in'][$c->from_id()] = $c->model();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $model;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
public static function add($fields, $import=false) {
|
2023-05-17 21:19:14 +02:00
|
|
|
global $DB;
|
2023-08-25 11:52:05 +02:00
|
|
|
$addable = ['line_id', 'type', 'layer', 'conditions', 'slot',
|
|
|
|
'competency_id', 'course_id', 'badge_id', 'continuation_id', 'span'];
|
2023-07-15 22:00:17 +02:00
|
|
|
$info = [ 'layer' => 0, ];
|
2023-08-24 23:02:41 +02:00
|
|
|
foreach ($addable as $f) {
|
|
|
|
if (array_key_exists($f, $fields)) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$info[$f] = $fields[$f];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$id = $DB->insert_record(self::TABLE, $info);
|
2023-06-27 07:33:27 +02:00
|
|
|
$item = self::findById($id);
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($item->type() == self::COURSE) {
|
|
|
|
// Signal the studyplan that a course has been added so it can be marked for csync cascading.
|
|
|
|
$item->studyline()->studyplan()->mark_csync_changed();
|
2023-06-27 07:33:27 +02:00
|
|
|
}
|
|
|
|
return $item;
|
2023-05-17 21:19:14 +02:00
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
public function edit($fields) {
|
2023-05-17 21:19:14 +02:00
|
|
|
global $DB;
|
2023-08-24 23:02:41 +02:00
|
|
|
$editable = ['conditions', 'course_id', 'continuation_id', 'span'];
|
2023-05-17 21:19:14 +02:00
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
$info = ['id' => $this->id, ];
|
|
|
|
foreach ($editable as $f) {
|
|
|
|
if (array_key_exists($f, $fields) && isset($fields[$f])) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$info[$f] = $fields[$f];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$DB->update_record(self::TABLE, $info);
|
2023-08-24 23:02:41 +02:00
|
|
|
//reload record after edit.
|
|
|
|
$this->r = $DB->get_record(self::TABLE, ['id' => $this->id], "*", MUST_EXIST);
|
2023-05-17 21:19:14 +02:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
public function isValid() {
|
|
|
|
// Check if referenced courses, badges and/or competencies still exist.
|
|
|
|
if ($this->r->type == static::COURSE) {
|
2023-05-17 21:19:14 +02:00
|
|
|
return courseinfo::exists($this->r->course_id);
|
2023-08-24 23:09:20 +02:00
|
|
|
} else if ($this->r->type == static::BADGE) {
|
2023-05-17 21:19:14 +02:00
|
|
|
return badgeinfo::exists($this->r->badge_id);
|
2023-08-24 23:09:20 +02:00
|
|
|
} else {
|
2023-05-17 21:19:14 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
public function delete($force=false) {
|
2023-05-17 21:19:14 +02:00
|
|
|
global $DB;
|
|
|
|
|
2023-08-25 09:44:34 +02:00
|
|
|
// Check if this item is referenced in a START item.
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($force) {
|
2023-08-25 09:44:34 +02:00
|
|
|
// Clear continuation id from any references to this item.
|
2023-08-24 23:02:41 +02:00
|
|
|
$records = $DB->get_records(self::TABLE, ['continuation_id' => $this->id]);
|
|
|
|
foreach ($records as $r) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$r->continuation_id = 0;
|
2023-08-24 23:02:41 +02:00
|
|
|
$DB->update_record(self::TABLE, $r);
|
2023-05-17 21:19:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($DB->count_records(self::TABLE, ['continuation_id' => $this->id]) > 0) {
|
2023-05-17 21:19:14 +02:00
|
|
|
return success::fail('Cannot remove: item is referenced by another item');
|
2023-08-25 09:33:42 +02:00
|
|
|
} else {
|
2023-08-25 09:44:34 +02:00
|
|
|
// Delete al related connections to this item.
|
2023-05-17 21:19:14 +02:00
|
|
|
studyitemconnection::clear($this->id);
|
2023-08-25 09:44:34 +02:00
|
|
|
// Delete all grade inclusion references to this item.
|
2023-08-24 23:02:41 +02:00
|
|
|
$DB->delete_records("local_treestudyplan_gradeinc", ['studyitem_id' => $this->id]);
|
2023-08-25 09:44:34 +02:00
|
|
|
// Delete the item itself.
|
2023-05-17 21:19:14 +02:00
|
|
|
$DB->delete_records(self::TABLE, ['id' => $this->id]);
|
|
|
|
|
|
|
|
return success::success();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/************************
|
|
|
|
* *
|
|
|
|
* reorder_studyitems *
|
|
|
|
* *
|
|
|
|
************************/
|
2023-08-24 23:02:41 +02:00
|
|
|
|
|
|
|
public static function reorder($resequence) {
|
2023-05-17 21:19:14 +02:00
|
|
|
global $DB;
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
foreach ($resequence as $sq) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$DB->update_record(self::TABLE, [
|
|
|
|
'id' => $sq['id'],
|
|
|
|
'line_id' => $sq['line_id'],
|
|
|
|
'slot' => $sq['slot'],
|
|
|
|
'layer' => $sq['layer'],
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return success::success();
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
public static function find_studyline_children(studyline $line) {
|
2023-05-17 21:19:14 +02:00
|
|
|
global $DB;
|
|
|
|
$list = [];
|
2023-08-24 23:02:41 +02:00
|
|
|
$ids = $DB->get_fieldset_select(self::TABLE, "id", "line_id = :line_id ORDER BY layer", ['line_id' => $line->id()]);
|
|
|
|
foreach ($ids as $id) {
|
|
|
|
$item = self::findById($id, $line);
|
2023-05-17 21:19:14 +02:00
|
|
|
$list[] = $item;
|
|
|
|
}
|
|
|
|
return $list;
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
private static function link_structure($value=VALUE_REQUIRED) {
|
2023-05-17 21:19:14 +02:00
|
|
|
return new \external_single_structure([
|
|
|
|
"id" => new \external_value(PARAM_INT, 'id of study item'),
|
|
|
|
"type" => new \external_value(PARAM_TEXT, 'type of study item'),
|
|
|
|
"completion" => completion::structure(),
|
2023-08-24 23:02:41 +02:00
|
|
|
"studyline" => new \external_value(PARAM_TEXT, 'reference label of studyline'),
|
2023-05-17 21:19:14 +02:00
|
|
|
"studyplan" => new \external_value(PARAM_TEXT, 'reference label of studyplan'),
|
|
|
|
], 'basic info of referenced studyitem', $value);
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
private function link_model($userid) {
|
2023-05-17 21:19:14 +02:00
|
|
|
global $DB;
|
2023-08-24 23:02:41 +02:00
|
|
|
$line = $DB->get_record(studyline::TABLE, ['id' => $this->r->line_id]);
|
|
|
|
$plan = $DB->get_record(studyplan::TABLE, ['id' => $line->studyplan_id]);
|
2023-05-17 21:19:14 +02:00
|
|
|
|
|
|
|
return [
|
|
|
|
"id" => $this->r->id,
|
|
|
|
"type" => $this->r->type,
|
2023-08-24 23:02:41 +02:00
|
|
|
"completion" => $this->completion($userid),
|
|
|
|
"studyline" => $line->name(),
|
2023-05-17 21:19:14 +02:00
|
|
|
"studyplan" => $plan->name(),
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
public static function user_structure($value=VALUE_REQUIRED) {
|
2023-05-17 21:19:14 +02:00
|
|
|
return new \external_single_structure([
|
|
|
|
"id" => new \external_value(PARAM_INT, 'id of study item'),
|
|
|
|
"type" => new \external_value(PARAM_TEXT, 'type of study item'),
|
|
|
|
"completion" => new \external_value(PARAM_TEXT, 'completion state (incomplete|progress|completed|excellent)'),
|
|
|
|
"slot" => new \external_value(PARAM_INT, 'slot in the study plan'),
|
|
|
|
"layer" => new \external_value(PARAM_INT, 'layer in the slot'),
|
2023-08-04 11:54:16 +02:00
|
|
|
"span" => new \external_value(PARAM_INT, 'how many periods the item spans'),
|
2023-05-17 21:19:14 +02:00
|
|
|
"course" => courseinfo::user_structure(VALUE_OPTIONAL),
|
|
|
|
"badge" => badgeinfo::user_structure(VALUE_OPTIONAL),
|
|
|
|
"continuation" => self::link_structure(VALUE_OPTIONAL),
|
|
|
|
"connections" => new \external_single_structure([
|
|
|
|
'in' => new \external_multiple_structure(studyitemconnection::structure()),
|
|
|
|
'out' => new \external_multiple_structure(studyitemconnection::structure()),
|
|
|
|
]),
|
2023-08-24 23:02:41 +02:00
|
|
|
], 'Study item info', $value);
|
2023-05-17 21:19:14 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
public function user_model($userid) {
|
2023-05-17 21:19:14 +02:00
|
|
|
global $CFG, $DB;
|
|
|
|
|
|
|
|
$model = [
|
|
|
|
'id' => $this->r->id,
|
|
|
|
'type' => $this->r->type,
|
|
|
|
'completion' => completion::label($this->completion($userid)),
|
|
|
|
'slot' => $this->r->slot,
|
|
|
|
'layer' => $this->r->layer,
|
2023-08-04 11:54:16 +02:00
|
|
|
'span' => $this->r->span,
|
2023-05-17 21:19:14 +02:00
|
|
|
'connections' => [
|
|
|
|
"in" => [],
|
|
|
|
"out" => [],
|
|
|
|
]
|
|
|
|
];
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
// Add badge info if available.
|
|
|
|
if (badgeinfo::exists($this->r->badge_id)) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$badge = new \core_badges\badge($this->r->badge_id);
|
|
|
|
$badgeinfo = new badgeinfo($badge);
|
|
|
|
$model['badge'] = $badgeinfo->user_model($userid);
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
// Add continuation_info if available.
|
|
|
|
if (self::exists($this->r->continuation_id)) {
|
2023-08-25 09:33:42 +02:00
|
|
|
$citem = self::findById($this->r->continuation_id);
|
|
|
|
$model['continuation'] = $citem->link_model($userid);
|
2023-05-17 21:19:14 +02:00
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
// Add course if available.
|
|
|
|
if (courseinfo::exists($this->r->course_id)) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$cinfo = $this->getcourseinfo();
|
2023-08-24 23:02:41 +02:00
|
|
|
$model['course'] = $cinfo->user_model($userid, $this->aggregator->usecorecompletioninfo());
|
2023-05-17 21:19:14 +02:00
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
// Add incoming and outgoing connection info.
|
2023-08-25 09:33:42 +02:00
|
|
|
$connout = studyitemconnection::find_outgoing($this->id);
|
|
|
|
foreach ($connout as $c) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$model['connections']['out'][$c->to_id()] = $c->model();
|
|
|
|
}
|
2023-08-25 09:33:42 +02:00
|
|
|
$connin = studyitemconnection::find_incoming($this->id);
|
|
|
|
foreach ($connin as $c) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$model['connections']['in'][$c->from_id()] = $c->model();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $model;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
public function getcourseinfo() {
|
|
|
|
if (empty($this->courseinfo) && courseinfo::exists($this->r->course_id)) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$this->courseinfo = new courseinfo($this->r->course_id, $this);
|
|
|
|
}
|
|
|
|
return $this->courseinfo;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function completion($userid) {
|
|
|
|
global $DB;
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($this->isValid()) {
|
|
|
|
if (strtolower($this->r->type) == 'course') {
|
2023-08-25 09:44:34 +02:00
|
|
|
// Determine competency by competency completion.
|
2023-05-17 21:19:14 +02:00
|
|
|
$courseinfo = $this->getcourseinfo();
|
2023-08-24 23:02:41 +02:00
|
|
|
return $this->aggregator->aggregate_course($courseinfo, $this, $userid);
|
2023-08-24 23:09:20 +02:00
|
|
|
} else if (strtolower($this->r->type) =='start') {
|
2023-05-17 21:19:14 +02:00
|
|
|
// Does not need to use aggregator.
|
2023-08-24 23:02:41 +02:00
|
|
|
// Either true, or the completion of the reference.
|
|
|
|
if (self::exists($this->r->continuation_id)) {
|
2023-08-25 09:33:42 +02:00
|
|
|
$citem = self::findById($this->r->continuation_id);
|
|
|
|
return $citem->completion($userid);
|
2023-05-17 21:19:14 +02:00
|
|
|
} else {
|
|
|
|
return completion::COMPLETED;
|
|
|
|
}
|
2023-08-24 23:09:20 +02:00
|
|
|
} else if (in_array(strtolower($this->r->type), ['junction', 'finish'])) {
|
2023-08-25 09:44:34 +02:00
|
|
|
// Completion of the linked items, according to the rule.
|
2023-08-25 09:33:42 +02:00
|
|
|
$incompleted = [];
|
2023-08-24 23:02:41 +02:00
|
|
|
// Retrieve incoming connections.
|
|
|
|
$incoming = $DB->get_records(studyitemconnection::TABLE, ['to_id' => $this->r->id]);
|
|
|
|
foreach ($incoming as $conn) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$item = self::findById($conn->from_id);
|
2023-08-25 09:33:42 +02:00
|
|
|
$incompleted[] = $item->completion($userid);
|
2023-05-17 21:19:14 +02:00
|
|
|
}
|
2023-08-25 09:33:42 +02:00
|
|
|
return $this->aggregator->aggregate_junction($incompleted, $this, $userid);
|
2023-08-24 23:09:20 +02:00
|
|
|
} else if (strtolower($this->r->type) =='badge') {
|
2023-05-17 21:19:14 +02:00
|
|
|
global $DB;
|
2023-08-25 09:44:34 +02:00
|
|
|
// Badge awarded.
|
2023-08-24 23:02:41 +02:00
|
|
|
if (badgeinfo::exists($this->r->badge_id)) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$badge = new \core_badges\badge($this->r->badge_id);
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($badge->is_issued($userid)) {
|
|
|
|
if ($badge->can_expire()) {
|
2023-08-25 09:44:34 +02:00
|
|
|
// Get the issued badges and check if any of them have not expired yet.
|
2023-08-25 11:52:05 +02:00
|
|
|
$badgesissued = $DB->get_records("badge_issued",
|
|
|
|
["badge_id" => $this->r->badge_id,
|
|
|
|
"user_id" => $userid]);
|
2023-05-17 21:19:14 +02:00
|
|
|
$notexpired = false;
|
|
|
|
$now = time();
|
2023-08-25 09:33:42 +02:00
|
|
|
foreach ($badgesissued as $bi) {
|
2023-08-24 23:02:41 +02:00
|
|
|
if ($bi->dateexpire == null || $bi->dateexpire > $now) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$notexpired = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2023-08-25 10:41:56 +02:00
|
|
|
return ($notexpired) ? completion::COMPLETED : completion::INCOMPLETE;
|
|
|
|
} else {
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::COMPLETED;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return completion::INCOMPLETE;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return completion::INCOMPLETE;
|
|
|
|
}
|
2023-08-24 23:09:20 +02:00
|
|
|
} else {
|
2023-08-25 09:44:34 +02:00
|
|
|
// Return incomplete for other types.
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::INCOMPLETE;
|
|
|
|
}
|
2023-08-24 23:09:20 +02:00
|
|
|
} else {
|
2023-08-25 09:44:34 +02:00
|
|
|
// Return incomplete for other types.
|
2023-05-17 21:19:14 +02:00
|
|
|
return completion::INCOMPLETE;
|
2023-08-24 23:02:41 +02:00
|
|
|
}
|
2023-05-17 21:19:14 +02:00
|
|
|
}
|
|
|
|
|
2023-08-25 09:33:42 +02:00
|
|
|
public function duplicate($newline) {
|
2023-05-17 21:19:14 +02:00
|
|
|
global $DB;
|
2023-08-25 09:44:34 +02:00
|
|
|
// Clone the database fields.
|
2023-05-17 21:19:14 +02:00
|
|
|
$fields = clone $this->r;
|
2023-08-25 09:44:34 +02:00
|
|
|
// Set new line id.
|
2023-05-17 21:19:14 +02:00
|
|
|
unset($fields->id);
|
2023-08-25 09:33:42 +02:00
|
|
|
$fields->line_id = $newline->id();
|
2023-08-24 23:02:41 +02:00
|
|
|
//create new record with the new data.
|
2023-05-17 21:19:14 +02:00
|
|
|
$id = $DB->insert_record(self::TABLE, (array)$fields);
|
2023-08-25 09:33:42 +02:00
|
|
|
$new = self::findById($id, $newline);
|
2023-05-17 21:19:14 +02:00
|
|
|
|
2023-08-25 09:44:34 +02:00
|
|
|
// Copy the grading info if relevant.
|
2023-05-17 21:19:14 +02:00
|
|
|
$gradables = gradeinfo::list_studyitem_gradables($this);
|
2023-08-24 23:02:41 +02:00
|
|
|
foreach ($gradables as $g) {
|
|
|
|
gradeinfo::include_grade($g->getGradeitem()->id, $new->id(), true);
|
2023-05-17 21:19:14 +02:00
|
|
|
}
|
|
|
|
return $new;
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
public function export_model() {
|
2023-05-17 21:19:14 +02:00
|
|
|
return $this->generate_model("export");
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
public static function import_item($model) {
|
2023-05-17 21:19:14 +02:00
|
|
|
unset($model["course_id"]);
|
|
|
|
unset($model["competency_id"]);
|
|
|
|
unset($model["badge_id"]);
|
|
|
|
unset($model["continuation_id"]);
|
2023-08-24 23:02:41 +02:00
|
|
|
if (isset($model["course"])) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$model["course_id"] = courseinfo::id_from_shortname(($model["course"]));
|
|
|
|
}
|
2023-08-24 23:02:41 +02:00
|
|
|
if (isset($model["badge"])) {
|
2023-05-17 21:19:14 +02:00
|
|
|
$model["badge_id"] = badgeinfo::id_from_name(($model["badge"]));
|
2023-08-24 23:02:41 +02:00
|
|
|
}
|
2023-05-17 21:19:14 +02:00
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
$item = self::add($model, true);
|
2023-05-17 21:19:14 +02:00
|
|
|
|
2023-08-24 23:02:41 +02:00
|
|
|
if (isset($model["course_id"])) {
|
2023-08-25 09:44:34 +02:00
|
|
|
// Attempt to import the gradables.
|
2023-08-24 23:02:41 +02:00
|
|
|
foreach ($model["gradables"] as $gradable) {
|
|
|
|
gradeinfo::import($item, $gradable);
|
2023-05-17 21:19:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $item;
|
|
|
|
}
|
|
|
|
|
2023-08-25 11:52:05 +02:00
|
|
|
}
|