369 lines
13 KiB
PHP
369 lines
13 KiB
PHP
<?php
|
|
namespace local_treestudyplan;
|
|
require_once($CFG->libdir.'/externallib.php');
|
|
|
|
class studyline {
|
|
public const SLOTSET_COMPETENCY = 'competencies';
|
|
public const SLOTSET_FILTER = 'filters';
|
|
|
|
public const COMPETENCY_TYPES = [
|
|
studyitem::COMPETENCY,
|
|
studyitem::COURSE,
|
|
];
|
|
public const FILTER_TYPES = [
|
|
studyitem::JUNCTION,
|
|
studyitem::BADGE,
|
|
studyitem::FINISH,
|
|
studyitem::START,
|
|
];
|
|
public const FILTER0_TYPES = [
|
|
studyitem::START,
|
|
];
|
|
|
|
public const TABLE = "local_treestudyplan_line";
|
|
|
|
private static $STUDYLINE_CACHE = [];
|
|
|
|
private $r; // Holds database record
|
|
private $id;
|
|
private $studyplan;
|
|
|
|
public function context(): \context {
|
|
return $this->studyplan->context();
|
|
}
|
|
|
|
public function getStudyplan() : studyplan {
|
|
return $this->studyplan;
|
|
}
|
|
|
|
public static function findById($id): self {
|
|
if(!array_key_exists($id,self::$STUDYLINE_CACHE)){
|
|
self::$STUDYLINE_CACHE[$id] = new self($id);
|
|
}
|
|
return self::$STUDYLINE_CACHE[$id];
|
|
}
|
|
|
|
private function __construct($id) {
|
|
global $DB;
|
|
$this->id = $id;
|
|
$this->r = $DB->get_record(self::TABLE,['id' => $id]);
|
|
$this->studyplan = studyplan::findById($this->r->studyplan_id);
|
|
}
|
|
|
|
public function id(){
|
|
return $this->id;
|
|
}
|
|
|
|
public function name(){
|
|
return $this->r->name;
|
|
}
|
|
public function shortname(){
|
|
return $this->r->shortname;
|
|
}
|
|
|
|
public static function editor_structure($value=VALUE_REQUIRED){
|
|
return new \external_single_structure([
|
|
"id" => new \external_value(PARAM_INT, 'id of studyline'),
|
|
"name" => new \external_value(PARAM_TEXT, 'shortname of studyline'),
|
|
"shortname"=> new \external_value(PARAM_TEXT, 'idnumber of studyline'),
|
|
"color"=> new \external_value(PARAM_TEXT, 'description of studyline'),
|
|
"sequence" => new \external_value(PARAM_INT, 'order of studyline'),
|
|
"slots" => new \external_multiple_structure(
|
|
new \external_single_structure([
|
|
self::SLOTSET_COMPETENCY => new \external_multiple_structure(studyitem::editor_structure(),'competency items',VALUE_OPTIONAL),
|
|
self::SLOTSET_FILTER => new \external_multiple_structure(studyitem::editor_structure(),'filter items'),
|
|
])
|
|
)
|
|
]);
|
|
}
|
|
|
|
public function editor_model(){
|
|
return $this->generate_model("editor");
|
|
}
|
|
|
|
protected 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)
|
|
global $DB;
|
|
|
|
$model = [
|
|
'id' => $this->r->id,
|
|
'name' => $this->r->name,
|
|
'shortname' => $this->r->shortname,
|
|
'color' => $this->r->color,
|
|
'sequence' => $this->r->sequence,
|
|
'slots' => [],
|
|
];
|
|
if($mode == "export"){
|
|
// Id and sequence are not used in export model
|
|
unset($model["id"]);
|
|
unset($model["sequence"]);
|
|
}
|
|
|
|
// Get the number of slots
|
|
// As a safety data integrity measure, if there are any items in a higher slot than currently allowed,
|
|
// make sure there are enought slots to account for them
|
|
// Alternatively, we could ensure that on reduction of slots, the items that no longer have a slot will be removed.
|
|
$max_slot = $DB->get_field_select(studyitem::TABLE,"MAX(slot)","line_id = :lineid",['lineid' => $this->id]);
|
|
$num_slots = max($this->studyplan->slots(),$max_slot +1);
|
|
|
|
// Create the required amount of slots
|
|
for($i=0; $i < $num_slots+1; $i++){
|
|
if($mode == "export") {
|
|
// Export mode does not separate between filter or competency type, since that is determined automatically
|
|
$slots = [];
|
|
} else {
|
|
if($i > 0) {
|
|
$slots = [self::SLOTSET_COMPETENCY => [], self::SLOTSET_FILTER => []];
|
|
} else {
|
|
$slots = [self::SLOTSET_FILTER => []];
|
|
}
|
|
}
|
|
$model['slots'][$i] = $slots;
|
|
}
|
|
|
|
$children = studyitem::find_studyline_children($this);
|
|
foreach($children as $c)
|
|
{
|
|
if($mode == "export") {
|
|
$model['slots'][$c->slot()][] = $c->export_model();
|
|
} else {
|
|
$slotset = null;
|
|
if($c->slot() > 0) {
|
|
if(in_array($c->type(),self::COMPETENCY_TYPES)) {
|
|
$slotset = self::SLOTSET_COMPETENCY;
|
|
} else if(in_array($c->type(),self::FILTER_TYPES)) {
|
|
$slotset = self::SLOTSET_FILTER;
|
|
}
|
|
}
|
|
else if(in_array($c->type(),self::FILTER0_TYPES)) {
|
|
$slotset = self::SLOTSET_FILTER;
|
|
}
|
|
if(isset($slotset)) {
|
|
$model['slots'][$c->slot()][$slotset][] = $c->editor_model();
|
|
}
|
|
}
|
|
}
|
|
return $model;
|
|
}
|
|
|
|
public static function add($fields){
|
|
global $DB;
|
|
|
|
if(!isset($fields['studyplan_id'])){
|
|
throw new \InvalidArgumentException("parameter 'studyplan_id' missing");
|
|
}
|
|
|
|
$studyplan_id = $fields['studyplan_id'];
|
|
$sqmax = $DB->get_field_select(self::TABLE,"MAX(sequence)","studyplan_id = :studyplan_id",['studyplan_id' => $studyplan_id]);
|
|
$addable = ['studyplan_id','name','shortname','color'];
|
|
$info = ['sequence' => $sqmax+1];
|
|
foreach($addable as $f){
|
|
if(array_key_exists($f,$fields)){
|
|
$info[$f] = $fields[$f];
|
|
}
|
|
}
|
|
$id = $DB->insert_record(self::TABLE, $info);
|
|
return self::findById($id);
|
|
}
|
|
|
|
public function edit($fields){
|
|
global $DB;
|
|
$editable = ['name','shortname','color'];
|
|
$info = ['id' => $this->id,];
|
|
foreach($editable as $f){
|
|
if(array_key_exists($f,$fields)){
|
|
$info[$f] = $fields[$f];
|
|
}
|
|
}
|
|
$DB->update_record(self::TABLE, $info);
|
|
//reload record after edit
|
|
$this->r = $DB->get_record(self::TABLE,['id' => $this->id],"*",MUST_EXIST);
|
|
return $this;
|
|
}
|
|
|
|
public function delete($force = false){
|
|
global $DB;
|
|
|
|
if($force){
|
|
$children = studyitem::find_studyline_children($this);
|
|
foreach($children as $c){
|
|
$c->delete($force);
|
|
}
|
|
}
|
|
// check if this item has study items in it
|
|
if($DB->count_records(studyitem::TABLE,['line_id' => $this->id]) > 0){
|
|
return success::fail('cannot delete studyline with items');
|
|
}
|
|
else
|
|
{
|
|
$DB->delete_records(self::TABLE, ['id' => $this->id]);
|
|
return success::success();
|
|
}
|
|
}
|
|
|
|
public static function reorder($resequence)
|
|
{
|
|
global $DB;
|
|
|
|
foreach($resequence as $sq)
|
|
{
|
|
$DB->update_record(self::TABLE, [
|
|
'id' => $sq['id'],
|
|
'sequence' => $sq['sequence'],
|
|
]);
|
|
}
|
|
|
|
return success::success();
|
|
}
|
|
|
|
public static function find_studyplan_children(studyplan $plan)
|
|
{
|
|
global $DB;
|
|
$list = [];
|
|
$ids = $DB->get_fieldset_select(self::TABLE,"id","studyplan_id = :plan_id ORDER BY sequence",['plan_id' => $plan->id()]);
|
|
foreach($ids as $id) {
|
|
$list[] = self::findById($id);
|
|
}
|
|
return $list;
|
|
}
|
|
|
|
public static function user_structure($value=VALUE_REQUIRED){
|
|
return new \external_single_structure([
|
|
"id" => new \external_value(PARAM_INT, 'id of studyline'),
|
|
"name" => new \external_value(PARAM_TEXT, 'shortname of studyline'),
|
|
"shortname"=> new \external_value(PARAM_TEXT, 'idnumber of studyline'),
|
|
"color"=> new \external_value(PARAM_TEXT, 'description of studyline'),
|
|
"sequence" => new \external_value(PARAM_INT, 'order of studyline'),
|
|
"slots" => new \external_multiple_structure(
|
|
new \external_single_structure([
|
|
self::SLOTSET_COMPETENCY => new \external_multiple_structure(studyitem::user_structure(),'competency items',VALUE_OPTIONAL),
|
|
self::SLOTSET_FILTER => new \external_multiple_structure(studyitem::user_structure(),'filter items'),
|
|
])
|
|
)
|
|
],'Studyline with user info',$value);
|
|
}
|
|
|
|
public function user_model($userid){
|
|
// TODO: Integrate this function into generate_model() for ease of maintenance
|
|
|
|
global $DB;
|
|
|
|
$model = [
|
|
'id' => $this->r->id,
|
|
'name' => $this->r->name,
|
|
'shortname' => $this->r->shortname,
|
|
'color' => $this->r->color,
|
|
'sequence' => $this->r->sequence,
|
|
'slots' => [],
|
|
];
|
|
|
|
// Get the number of slots
|
|
// As a safety data integrity measure, if there are any items in a higher slot than currently allowed,
|
|
// make sure there are enought slots to account for them
|
|
// Alternatively, we could ensure that on reduction of slots, the items that no longer have a slot will be removed.
|
|
$max_slot = $DB->get_field_select(studyitem::TABLE,"MAX(slot)","line_id = :lineid",['lineid' => $this->id]);
|
|
$num_slots = max($this->studyplan->slots(),$max_slot +1);
|
|
|
|
// Create the required amount of slots
|
|
for($i=0; $i < $num_slots+1; $i++){
|
|
if($i > 0) {
|
|
$slots = [self::SLOTSET_COMPETENCY => [], self::SLOTSET_FILTER => []];
|
|
} else {
|
|
$slots = [self::SLOTSET_FILTER => []];
|
|
}
|
|
$model['slots'][$i] = $slots;
|
|
}
|
|
|
|
$children = studyitem::find_studyline_children($this);
|
|
foreach($children as $c)
|
|
{
|
|
if($c->isValid()){
|
|
$slotset = null;
|
|
if($c->slot() > 0) {
|
|
if(in_array($c->type(),self::COMPETENCY_TYPES)) {
|
|
$slotset = self::SLOTSET_COMPETENCY;
|
|
} else if(in_array($c->type(),self::FILTER_TYPES)) {
|
|
$slotset = self::SLOTSET_FILTER;
|
|
}
|
|
}
|
|
else if(in_array($c->type(),self::FILTER0_TYPES)) {
|
|
$slotset = self::SLOTSET_FILTER;
|
|
}
|
|
if(isset($slotset)) {
|
|
$model['slots'][$c->slot()][$slotset][] = $c->user_model($userid);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
return $model;
|
|
|
|
}
|
|
|
|
public function duplicate($new_studyplan,&$translation){
|
|
global $DB;
|
|
|
|
// clone the database fields
|
|
$fields = clone $this->r;
|
|
// set new studyplan id
|
|
unset($fields->id);
|
|
$fields->studyplan_id = $new_studyplan->id();
|
|
// create new record with the new data
|
|
$id = $DB->insert_record(self::TABLE, (array)$fields);
|
|
$new = self::findById($id);
|
|
|
|
// Next copy all the study items for this studyline
|
|
// and record the original and copy id's in the $translation array
|
|
// so the calling function can connect the new studyitems as required
|
|
$children = studyitem::find_studyline_children($this);
|
|
$translation = [];
|
|
foreach($children as $c)
|
|
{
|
|
$newchild = $c->duplicate($this);
|
|
$translation[$c->id()] = $newchild->id();
|
|
}
|
|
return $new;
|
|
}
|
|
|
|
public function export_model()
|
|
{
|
|
return $this->generate_model("export");
|
|
}
|
|
|
|
public function import_studyitems($model,&$itemtranslation,&$connections){
|
|
global $DB;
|
|
foreach($model as $slot=>$slotmodel)
|
|
{
|
|
$courselayer = 0;
|
|
$filterlayer = 0;
|
|
foreach($slotmodel as $itemmodel)
|
|
{
|
|
if($itemmodel["type"] == "course" || $itemmodel["type"] == "competency"){
|
|
$itemmodel["layer"] = $courselayer;
|
|
$courselayer++;
|
|
}else {
|
|
$itemmodel["layer"] = $filterlayer;
|
|
$filterlayer++;
|
|
}
|
|
|
|
$itemmodel["slot"] = $slot;
|
|
$itemmodel["line_id"] = $this->id();
|
|
$item = studyitem::import_item($itemmodel);
|
|
|
|
if(!empty($item)){
|
|
$itemtranslation[$itemmodel["id"]] = $item->id();
|
|
|
|
if(count($itemmodel["connections"]) > 0){
|
|
if(! isset($connections[$item->id()]) || ! is_array($connections[$item->id()])){
|
|
$connections[$item->id()] = [];
|
|
}
|
|
foreach($itemmodel["connections"] as $to_id){
|
|
$connections[$item->id()][] = $to_id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |